[
  {
    "path": ".circleci/config.yml",
    "content": "# Golang CircleCI 2.0 configuration file\n#\n# Check https://circleci.com/docs/2.0/language-go/ for more details\nversion: 2.1\n\njobs:\n  \"golang-1_15\": &template\n    machine:\n      # https://circleci.com/docs/2.0/configuration-reference/#available-machine-images\n      image: ubuntu-2004:202010-01\n      # docker_layer_caching: true\n\n    # https://circleci.com/docs/2.0/configuration-reference/#resource_class\n    resource_class: medium\n\n    # Leave working directory unspecified and use defaults:\n    # https://circleci.com/blog/go-v1.11-modules-and-circleci/\n    # working_directory: /go/src/github.com/golang-migrate/migrate\n\n    environment:\n      GO111MODULE: \"on\"\n      GO_VERSION: \"1.15.x\"\n\n    steps:\n      # - setup_remote_docker:\n      #    version: 19.03.13\n          # docker_layer_caching: true\n      - run: curl -sL -o ~/bin/gimme https://raw.githubusercontent.com/travis-ci/gimme/master/gimme\n      - run: curl -sfL -o ~/bin/golangci-lint.sh https://install.goreleaser.com/github.com/golangci/golangci-lint.sh\n      - run: chmod +x ~/bin/gimme ~/bin/golangci-lint.sh\n      - run: eval \"$(gimme $GO_VERSION)\"\n      - run: golangci-lint.sh -b ~/bin v1.37.0\n      - checkout\n      - restore_cache:\n          keys:\n            - go-mod-v1-{{ arch }}-{{ checksum \"go.sum\" }}\n      - run: golangci-lint run\n      - run: make test COVERAGE_DIR=/tmp/coverage\n      - save_cache:\n          key: go-mod-v1-{{ arch }}-{{ checksum \"go.sum\" }}\n          paths:\n            - \"/go/pkg/mod\"\n      - run: go get github.com/mattn/goveralls\n      - run: goveralls -service=circle-ci -coverprofile /tmp/coverage/combined.txt\n\n  \"golang-1_16\":\n    <<: *template\n    environment:\n      GO_VERSION: \"1.16.x\"\n\nworkflows:\n  version: 2\n  build:\n    jobs:\n      - \"golang-1_15\"\n      - \"golang-1_16\"\n"
  },
  {
    "path": ".dockerignore",
    "content": "# Project\nFAQ.md\nREADME.md\nLICENSE\n.gitignore\n.travis.yml\nCONTRIBUTING.md\nMIGRATIONS.md\ndocker-deploy.sh\n\n# Golang\ntesting\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\n\n---\n\n**Describe the Bug**\nA clear and concise description of what the bug is.\n\n**Steps to Reproduce**\nSteps to reproduce the behavior:\n1. My migrations look like '...'\n2. I ran migrate with the following options '....'\n3. See error\n\n**Expected Behavior**\nA clear and concise description of what you expected to happen.\n\n**Migrate Version**\ne.g. v3.4.0\nObtained by running: `migrate -version`\n\n**Loaded Source Drivers**\ne.g. s3, github, go-bindata, gcs, file\nObtained by running: `migrate -help`\n\n**Loaded Database Drivers**\ne.g. spanner, stub, clickhouse, cockroachdb, crdb-postgres, postgres, postgresql, pgx, redshift, cassandra, cockroach, mysql\nObtained by running: `migrate -help`\n\n**Go Version**\ne.g. go version go1.11 linux/amd64\nObtained by running: `go version`\n\n**Stacktrace**\nPlease provide if available\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "content": "name: CI\n\non:\n  push:\n  pull_request:\n\njobs:\n  lint:\n    name: lint\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-go@v5\n        with:\n          go-version: \"1.25.x\"\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@v9\n        with:\n          version: v2.6.2\n\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        go: [\"1.24.x\", \"1.25.x\"]\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: actions/setup-go@v5\n        with:\n          go-version: ${{ matrix.go }}\n\n      - name: Run test\n        run: make test COVERAGE_DIR=/tmp/coverage\n\n      - name: Send goveralls coverage\n        uses: shogo82148/actions-goveralls@v1\n        with:\n          path-to-profile: /tmp/coverage/combined.txt\n          flag-name: Go-${{ matrix.go }}\n          parallel: true\n\n  check-coverage:\n    name: Check coverage\n    needs: [test]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: shogo82148/actions-goveralls@v1\n        with:\n          parallel-finished: true\n\n  goreleaser:\n    name: Release a new version\n    needs: [lint, test]\n    runs-on: ubuntu-latest\n    environment: GoReleaser\n    # This job only runs when\n    # 1. When the previous `lint` and `test` jobs has completed successfully\n    # 2. When the repository is not a fork, i.e. it will only run on the official golang-migrate/migrate\n    # 3. When the workflow is triggered by a tag with `v` prefix\n    if: ${{ success() && github.repository == 'golang-migrate/migrate' && startsWith(github.ref, 'refs/tags/v') }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: 2.7\n      - uses: actions/setup-go@v5\n        with:\n          go-version: \"1.25.x\"\n\n      - uses: docker/setup-qemu-action@v3\n      - uses: docker/setup-buildx-action@v3\n      - uses: docker/login-action@v3\n        with:\n          username: golangmigrate\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - run: echo \"SOURCE=$(make echo-source)\" >> $GITHUB_ENV\n      - run: echo \"DATABASE=$(make echo-database)\" >> $GITHUB_ENV\n\n      - uses: goreleaser/goreleaser-action@v5\n        with:\n          version: latest\n          args: release --clean\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - run: gem install package_cloud\n      - run: package_cloud push golang-migrate/migrate/ubuntu/bionic dist/migrate.linux-amd64.deb\n        env:\n          PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }}\n      - run: package_cloud push golang-migrate/migrate/ubuntu/focal dist/migrate.linux-amd64.deb\n        env:\n          PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }}\n      - run: package_cloud push golang-migrate/migrate/ubuntu/jammy dist/migrate.linux-amd64.deb\n        env:\n          PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }}\n      - run: package_cloud push golang-migrate/migrate/ubuntu/noble dist/migrate.linux-amd64.deb\n        env:\n          PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }}\n      - run: package_cloud push golang-migrate/migrate/debian/buster dist/migrate.linux-amd64.deb\n        env:\n          PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }}\n      - run: package_cloud push golang-migrate/migrate/debian/bullseye dist/migrate.linux-amd64.deb\n        env:\n          PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }}\n      - run: package_cloud push golang-migrate/migrate/debian/bookworm dist/migrate.linux-amd64.deb\n        env:\n          PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\ncli/build\ncli/cli\ncli/migrate\n.coverage\n.godoc.pid\nvendor/\n.vscode/\n.idea\ndist/\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nlinters:\n  enable:\n    - goconst\n    - misspell\n    - nakedret\n    - prealloc\n    - revive\n    - unconvert\n    - unparam\n  settings:\n    misspell:\n      locale: US\n    revive:\n      rules:\n        - name: redundant-build-tag\n  exclusions:\n    generated: lax\n    rules:\n      - path: (.+)\\.go$\n        text: G104\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nissues:\n  max-issues-per-linter: 0\n  max-same-issues: 0\nformatters:\n  enable:\n    - gofmt\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "project_name: migrate\nbefore:\n  hooks:\n    - go mod tidy\nbuilds:\n  - env:\n      - CGO_ENABLED=0\n    goos:\n      - linux\n      - windows\n      - darwin\n    goarch:\n      - amd64\n      - arm\n      - arm64\n      - 386\n    goarm:\n      - 7\n    main: ./cmd/migrate\n    ldflags:\n      - '-w -s -X main.Version={{ .Version }} -extldflags \"static\"'\n    flags:\n      - \"-tags={{ .Env.DATABASE }} {{ .Env.SOURCE }}\"\n      - \"-trimpath\"\nnfpms:\n  - homepage: \"https://github.com/golang-migrate/migrate\"\n    maintainer: \"dhui@users.noreply.github.com\"\n    license: MIT\n    description: \"Database migrations\"\n    formats:\n      - deb\n    file_name_template: \"{{ .ProjectName }}.{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}\"\ndockers:\n  - goos: linux\n    goarch: amd64\n    dockerfile: Dockerfile.github-actions\n    use: buildx\n    ids:\n      - migrate\n    image_templates:\n      - 'migrate/migrate:{{ .Tag }}-amd64'\n    build_flag_templates:\n      - '--label=org.opencontainers.image.created={{ .Date }}'\n      - '--label=org.opencontainers.image.title={{ .ProjectName }}'\n      - '--label=org.opencontainers.image.revision={{ .FullCommit }}'\n      - '--label=org.opencontainers.image.version={{ .Version }}'\n      - \"--label=org.opencontainers.image.source={{ .GitURL }}\"\n      - \"--platform=linux/amd64\"\n  - goos: linux\n    goarch: arm64\n    dockerfile: Dockerfile.github-actions\n    use: buildx\n    ids:\n      - migrate\n    image_templates:\n      - 'migrate/migrate:{{ .Tag }}-arm64'\n    build_flag_templates:\n      - '--label=org.opencontainers.image.created={{ .Date }}'\n      - '--label=org.opencontainers.image.title={{ .ProjectName }}'\n      - '--label=org.opencontainers.image.revision={{ .FullCommit }}'\n      - '--label=org.opencontainers.image.version={{ .Version }}'\n      - \"--label=org.opencontainers.image.source={{ .GitURL }}\"\n      - \"--platform=linux/arm64\"\n\ndocker_manifests:\n- name_template: 'migrate/migrate:{{ .Tag }}'\n  image_templates:\n  - 'migrate/migrate:{{ .Tag }}-amd64'\n  - 'migrate/migrate:{{ .Tag }}-arm64'\n- name_template: 'migrate/migrate:{{ .Major }}'\n  image_templates:\n  - 'migrate/migrate:{{ .Tag }}-amd64'\n  - 'migrate/migrate:{{ .Tag }}-arm64'\n- name_template: 'migrate/migrate:latest'\n  image_templates:\n  - 'migrate/migrate:{{ .Tag }}-amd64'\n  - 'migrate/migrate:{{ .Tag }}-arm64'\narchives:\n  - name_template: \"{{ .ProjectName }}.{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}\"\n    format_overrides:\n      - goos: windows\n        format: zip\nchecksum:\n  name_template: 'sha256sum.txt'\nrelease:\n  draft: true\n  prerelease: auto\nsource:\n  enabled: true\n  format: zip\nchangelog:\n  skip: false\n  sort: asc\n  filters:\n    exclude:\n      - '^docs:'\n      - '^test:'\n      - Merge pull request\n      - Merge branch\n      - go mod tidy\nsnapshot:\n  name_template: \"{{ .Tag }}-next\"\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: go\nsudo: required\n\nmatrix:\n  allow_failures:\n    - go: master\n  include:\n    # Supported versions of Go: https://golang.org/dl/\n    - go: \"1.14.x\"\n    - go: \"1.15.x\"\n    - go: master\n\ngo_import_path: github.com/golang-migrate/migrate\n\nenv:\n  global:\n    - GO111MODULE=on\n    - MIGRATE_TEST_CONTAINER_BOOT_TIMEOUT=60\n    - DOCKER_USERNAME=golangmigrate\n    -   secure: \"oSOznzUrgr5h45qW4PONkREpisPAt40tnM+KFWtS/Ggu5UI2Ie0CmyYXWuBjbt7B97a4yN9Qzmn8FxJHJ7kk+ABOi3muhkxeIhr6esXbzHhX/Jhv0mj1xkzX7KoVN9oHBz3cOI/QeRyEAO68xjDHNE2kby4RTT9VBt6TQUakKVkqI5qkqLBTADepCjVC+9XhxVxUNyeWKU8ormaUfJBjoNVoDlwXekUPnJenfmfZqXxUInvBCfUyp7Pq+kurBORmg4yc6qOlRYuK67Xw+i5xpjbZouNlXPk0rq7pPy5zjhmZQ3kImoFPvNMeKViDcI6kSIJKtjdhms9/g/6MgXS9HlL5kFy8tYKbsyiHnHB1BsvaLAKXctbUZFDPstgMPADfnad2kZXPrNqIhfWKZrGRWidawCYJ1sKKwYxLMKrtA0umqgMoL90MmBOELhuGmvMV0cFJB+zo+K2YWjEiMGd8xRb5mC5aAy0ZcCehO46jGtpr217EJmMF8Ywr7cFqM2Shg5U2jev9qUpYiXwmPnJKDuoT2ZHuHmPgFIkYiWC5yeJnnmG5bed1sKBp93AFrJX+1Rx5oC4BpNegewmBZKpOSwls/D1uMAeQK3dPmQHLsT6o2VBLfeDGr+zY0R85ywwPZCv00vGol02zYoTqN7eFqr6Qhjr/qx5K1nnxJdFK3Ts=\"\n\nservices:\n  - docker\n\ncache:\n  directories:\n    - $GOPATH/pkg\n\n\nbefore_install:\n  # Update docker to latest version: https://docs.travis-ci.com/user/docker/#installing-a-newer-docker-version\n  - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -\n  - sudo add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\"\n  - sudo apt-get update\n  - sudo apt-get -y -o Dpkg::Options::=\"--force-confnew\" install docker-ce\n  # Install golangci-lint\n  - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.30.0\n  - echo \"TRAVIS_GO_VERSION=${TRAVIS_GO_VERSION}\"\n\ninstall:\n  - go get github.com/mattn/goveralls\n\nscript:\n  - golangci-lint run\n  - make test COVERAGE_DIR=/tmp/coverage\n\nafter_success:\n  - goveralls -service=travis-ci -coverprofile /tmp/coverage/combined.txt\n  - make list-external-deps > dependency_tree.txt && cat dependency_tree.txt\n  - make build-cli\n  - gem install --no-document fpm\n  - fpm -s dir -t deb -n migrate -v \"$(git describe --tags 2>/dev/null | cut -c 2-)\" --license MIT -m dhui@users.noreply.github.com --url https://github.com/golang-migrate/migrate --description='Database migrations' -a amd64 -p migrate.$(git describe --tags 2>/dev/null | cut -c 2-).deb --deb-no-default-config-files -f -C cli/build migrate.linux-amd64=/usr/local/bin/migrate\n\ndeploy:\n  - provider: releases\n    api_key:\n      secure: hWH1HLPpzpfA8pXQ93T1qKQVFSpQp0as/JLQ7D91jHuJ8p+RxVeqblDrR6HQY/95R/nyiE9GJmvUolSuw5h449LSrGxPtVWhdh6EnkxlQHlen5XeMhVjRjFV0sE9qGe8v7uAkiTfRO61ktTWHrEAvw5qpyqnNISodmZS78XIasPODQbNlzwINhWhDTHIjXGb4FpizYaL3OGCanrxfR9fQyCaqKGGBjRq3Mfq8U6Yd4mApmsE+uJxgaZV8K5zBqpkSzQRWhcVGNL5DuLsU3gfSJOo7kZeA2G71SHffH577dBoqtCZ4VFv169CoUZehLWCb+7XKJZmHXVujCURATSySLGUOPc6EoLFAn3YtsCA04mS4bZVo5FZPWVwfhjmkhtDR4f6wscKp7r1HsFHSOgm59QfETQdrn4MnZ44H2Jd39axqndn5DvK9EcZVjPHynOPnueXP2u6mTuUgh2VyyWBCDO3CNo0fGlo7VJI69IkIWNSD87K9cHZWYMClyKZkUzS+PmRAhHRYbVd+9ZjKOmnU36kUHNDG/ft1D4ogsY+rhVtXB4lgWDM5adri+EIScYdYnB1/pQexLBigcJY9uE7nQTR0U6QgVNYvun7uRNs40E0c4voSfmPdFO0FlOD2y1oQhnaXfWLbu9nMcTcs4RFGrcC7NzkUN4/WjG8s285V6w=\n    skip_cleanup: true\n    on:\n      go: \"1.15.x\"\n      repo: golang-migrate/migrate\n      tags: true\n    file:\n      - cli/build/migrate.linux-amd64.tar.gz\n      - cli/build/migrate.linux-armv7.tar.gz\n      - cli/build/migrate.linux-arm64.tar.gz\n      - cli/build/migrate.darwin-amd64.tar.gz\n      - cli/build/migrate.windows-amd64.exe.tar.gz\n      - cli/build/migrate.windows-386.exe.tar.gz\n      - cli/build/sha256sum.txt\n      - dependency_tree.txt\n  - provider: packagecloud\n    repository: migrate\n    username: golang-migrate\n    token:\n      secure: aICwu3gJ1sJ1QVCD3elpg+Jxzt4P+Zj1uoh5f0sOwnjDNIZ4FwUT1cMrWloP8P2KD0iyCOawuZER27o/kQ21oX2OxHvQbYPReA2znLm7lHzCmypAAOHPxpgnQ4rMGHHJXd+OsxtdclGs67c+EbdBfoRRbK400Qz/vjPJEDeH4mh02ZHC2nw4Nk/wV4jjBIkIt9dGEx6NgOA17FCMa3MaPHlHeFIzU7IfTlDHbS0mCCYbg/wafWBWcbGqtZLWAYtJDmfjrAStmDLdAX5J5PsB7taGSGPZHmPmpGoVgrKt/tb9Xz1rFBGslTpGROOiO4CiMAvkEKFn8mxrBGjfSBqp7Dp3eeSalKXB1DJAbEXx2sEbMcvmnoR9o43meaAn+ZRts8lRL8S/skBloe6Nk8bx3NlJCGB9WPK1G56b7c/fZnJxQbrCw6hxDfbZwm8S2YPviFTo/z1BfZDhRsL74reKsN2kgnGo2W/k38vvzIpsssQ9DHN1b0TLCxolCNPtQ7oHcQ1ohcjP2UgYXk0FhqDoL+9LQva/DU4N9sKH0UbAaqsMVSErLeG8A4aauuFcVrWRBaDYyTag4dQqzTulEy7iru2kDDIBgSQ1gMW/yoBOIPK4oi6MtbTf1X39fzXFLS1cDd3LW61yAu3YrbjAetpfx2frIvrRAiL9TxWA1gnrs5o=\n    dist: ubuntu/xenial\n    package_glob: '*.deb'\n    skip_cleanup: true\n    on:\n      go: \"1.15.x\"\n      repo: golang-migrate/migrate\n      tags: true\n  - provider: packagecloud\n    repository: migrate\n    username: golang-migrate\n    token:\n      secure: aICwu3gJ1sJ1QVCD3elpg+Jxzt4P+Zj1uoh5f0sOwnjDNIZ4FwUT1cMrWloP8P2KD0iyCOawuZER27o/kQ21oX2OxHvQbYPReA2znLm7lHzCmypAAOHPxpgnQ4rMGHHJXd+OsxtdclGs67c+EbdBfoRRbK400Qz/vjPJEDeH4mh02ZHC2nw4Nk/wV4jjBIkIt9dGEx6NgOA17FCMa3MaPHlHeFIzU7IfTlDHbS0mCCYbg/wafWBWcbGqtZLWAYtJDmfjrAStmDLdAX5J5PsB7taGSGPZHmPmpGoVgrKt/tb9Xz1rFBGslTpGROOiO4CiMAvkEKFn8mxrBGjfSBqp7Dp3eeSalKXB1DJAbEXx2sEbMcvmnoR9o43meaAn+ZRts8lRL8S/skBloe6Nk8bx3NlJCGB9WPK1G56b7c/fZnJxQbrCw6hxDfbZwm8S2YPviFTo/z1BfZDhRsL74reKsN2kgnGo2W/k38vvzIpsssQ9DHN1b0TLCxolCNPtQ7oHcQ1ohcjP2UgYXk0FhqDoL+9LQva/DU4N9sKH0UbAaqsMVSErLeG8A4aauuFcVrWRBaDYyTag4dQqzTulEy7iru2kDDIBgSQ1gMW/yoBOIPK4oi6MtbTf1X39fzXFLS1cDd3LW61yAu3YrbjAetpfx2frIvrRAiL9TxWA1gnrs5o=\n    dist: ubuntu/bionic\n    package_glob: '*.deb'\n    skip_cleanup: true\n    on:\n      go: \"1.15.x\"\n      repo: golang-migrate/migrate\n      tags: true\n  - provider: packagecloud\n    repository: migrate\n    username: golang-migrate\n    token:\n      secure: aICwu3gJ1sJ1QVCD3elpg+Jxzt4P+Zj1uoh5f0sOwnjDNIZ4FwUT1cMrWloP8P2KD0iyCOawuZER27o/kQ21oX2OxHvQbYPReA2znLm7lHzCmypAAOHPxpgnQ4rMGHHJXd+OsxtdclGs67c+EbdBfoRRbK400Qz/vjPJEDeH4mh02ZHC2nw4Nk/wV4jjBIkIt9dGEx6NgOA17FCMa3MaPHlHeFIzU7IfTlDHbS0mCCYbg/wafWBWcbGqtZLWAYtJDmfjrAStmDLdAX5J5PsB7taGSGPZHmPmpGoVgrKt/tb9Xz1rFBGslTpGROOiO4CiMAvkEKFn8mxrBGjfSBqp7Dp3eeSalKXB1DJAbEXx2sEbMcvmnoR9o43meaAn+ZRts8lRL8S/skBloe6Nk8bx3NlJCGB9WPK1G56b7c/fZnJxQbrCw6hxDfbZwm8S2YPviFTo/z1BfZDhRsL74reKsN2kgnGo2W/k38vvzIpsssQ9DHN1b0TLCxolCNPtQ7oHcQ1ohcjP2UgYXk0FhqDoL+9LQva/DU4N9sKH0UbAaqsMVSErLeG8A4aauuFcVrWRBaDYyTag4dQqzTulEy7iru2kDDIBgSQ1gMW/yoBOIPK4oi6MtbTf1X39fzXFLS1cDd3LW61yAu3YrbjAetpfx2frIvrRAiL9TxWA1gnrs5o=\n    dist: ubuntu/focal\n    package_glob: '*.deb'\n    skip_cleanup: true\n    on:\n      go: \"1.15.x\"\n      repo: golang-migrate/migrate\n      tags: true\n  - provider: packagecloud\n    repository: migrate\n    username: golang-migrate\n    token:\n      secure: aICwu3gJ1sJ1QVCD3elpg+Jxzt4P+Zj1uoh5f0sOwnjDNIZ4FwUT1cMrWloP8P2KD0iyCOawuZER27o/kQ21oX2OxHvQbYPReA2znLm7lHzCmypAAOHPxpgnQ4rMGHHJXd+OsxtdclGs67c+EbdBfoRRbK400Qz/vjPJEDeH4mh02ZHC2nw4Nk/wV4jjBIkIt9dGEx6NgOA17FCMa3MaPHlHeFIzU7IfTlDHbS0mCCYbg/wafWBWcbGqtZLWAYtJDmfjrAStmDLdAX5J5PsB7taGSGPZHmPmpGoVgrKt/tb9Xz1rFBGslTpGROOiO4CiMAvkEKFn8mxrBGjfSBqp7Dp3eeSalKXB1DJAbEXx2sEbMcvmnoR9o43meaAn+ZRts8lRL8S/skBloe6Nk8bx3NlJCGB9WPK1G56b7c/fZnJxQbrCw6hxDfbZwm8S2YPviFTo/z1BfZDhRsL74reKsN2kgnGo2W/k38vvzIpsssQ9DHN1b0TLCxolCNPtQ7oHcQ1ohcjP2UgYXk0FhqDoL+9LQva/DU4N9sKH0UbAaqsMVSErLeG8A4aauuFcVrWRBaDYyTag4dQqzTulEy7iru2kDDIBgSQ1gMW/yoBOIPK4oi6MtbTf1X39fzXFLS1cDd3LW61yAu3YrbjAetpfx2frIvrRAiL9TxWA1gnrs5o=\n    dist: debian/stretch\n    package_glob: '*.deb'\n    skip_cleanup: true\n    on:\n      go: \"1.15.x\"\n      repo: golang-migrate/migrate\n      tags: true\n  - provider: packagecloud\n    repository: migrate\n    username: golang-migrate\n    token:\n      secure: aICwu3gJ1sJ1QVCD3elpg+Jxzt4P+Zj1uoh5f0sOwnjDNIZ4FwUT1cMrWloP8P2KD0iyCOawuZER27o/kQ21oX2OxHvQbYPReA2znLm7lHzCmypAAOHPxpgnQ4rMGHHJXd+OsxtdclGs67c+EbdBfoRRbK400Qz/vjPJEDeH4mh02ZHC2nw4Nk/wV4jjBIkIt9dGEx6NgOA17FCMa3MaPHlHeFIzU7IfTlDHbS0mCCYbg/wafWBWcbGqtZLWAYtJDmfjrAStmDLdAX5J5PsB7taGSGPZHmPmpGoVgrKt/tb9Xz1rFBGslTpGROOiO4CiMAvkEKFn8mxrBGjfSBqp7Dp3eeSalKXB1DJAbEXx2sEbMcvmnoR9o43meaAn+ZRts8lRL8S/skBloe6Nk8bx3NlJCGB9WPK1G56b7c/fZnJxQbrCw6hxDfbZwm8S2YPviFTo/z1BfZDhRsL74reKsN2kgnGo2W/k38vvzIpsssQ9DHN1b0TLCxolCNPtQ7oHcQ1ohcjP2UgYXk0FhqDoL+9LQva/DU4N9sKH0UbAaqsMVSErLeG8A4aauuFcVrWRBaDYyTag4dQqzTulEy7iru2kDDIBgSQ1gMW/yoBOIPK4oi6MtbTf1X39fzXFLS1cDd3LW61yAu3YrbjAetpfx2frIvrRAiL9TxWA1gnrs5o=\n    dist: debian/buster\n    package_glob: '*.deb'\n    skip_cleanup: true\n    on:\n      go: \"1.15.x\"\n      repo: golang-migrate/migrate\n      tags: true\n  - provider: script\n    script: ./docker-deploy.sh\n    skip_cleanup: true\n    on:\n      go: \"1.15.x\"\n      repo: golang-migrate/migrate\n      tags: true\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Development, Testing and Contributing\n\n  1. Make sure you have a running Docker daemon\n     (Install for [MacOS](https://docs.docker.com/docker-for-mac/))\n  1. Use a version of Go that supports [modules](https://golang.org/cmd/go/#hdr-Modules__module_versions__and_more) (e.g. Go 1.11+)\n  1. Fork this repo and `git clone` somewhere to `$GOPATH/src/github.com/golang-migrate/migrate`\n      * Ensure that [Go modules are enabled](https://golang.org/cmd/go/#hdr-Preliminary_module_support) (e.g. your repo path or the `GO111MODULE` environment variable are set correctly)\n  1. Install [golangci-lint](https://github.com/golangci/golangci-lint#install)\n  1. Run the linter: `golangci-lint run`\n  1. Confirm tests are working: `make test-short`\n  1. Write awesome code ...\n  1. `make test` to run all tests against all database versions\n  1. Push code and open Pull Request\n \nSome more helpful commands:\n\n  * You can specify which database/ source tests to run:\n    `make test-short SOURCE='file go_bindata' DATABASE='postgres cassandra'`\n  * After `make test`, run `make html-coverage` which opens a shiny test coverage overview.\n  * `make build-cli` builds the CLI in directory `cli/build/`.\n  * `make list-external-deps` lists all external dependencies for each package\n  * `make docs && make open-docs` opens godoc in your browser, `make kill-docs` kills the godoc server.\n    Repeatedly call `make docs` to refresh the server.\n  * Set the `DOCKER_API_VERSION` environment variable to the latest supported version if you get errors regarding the docker client API version being too new.\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM golang:1.25-alpine3.21 AS builder\nARG VERSION\n\nRUN apk add --no-cache git gcc musl-dev make\n\nWORKDIR /go/src/github.com/golang-migrate/migrate\n\nENV GO111MODULE=on\n\nCOPY go.mod go.sum ./\n\nRUN go mod download\n\nCOPY . ./\n\nRUN make build-docker\n\nFROM alpine:3.21\n\nRUN apk add --no-cache ca-certificates\n\nCOPY --from=builder /go/src/github.com/golang-migrate/migrate/build/migrate.linux-386 /usr/local/bin/migrate\nRUN ln -s /usr/local/bin/migrate /migrate\n\nENTRYPOINT [\"migrate\"]\nCMD [\"--help\"]\n"
  },
  {
    "path": "Dockerfile.circleci",
    "content": "ARG DOCKER_IMAGE\nFROM $DOCKER_IMAGE\n\nRUN apk add --no-cache git gcc musl-dev make\n\nWORKDIR /go/src/github.com/golang-migrate/migrate\n\nENV GO111MODULE=on\nENV COVERAGE_DIR=/tmp/coverage\n\nCOPY go.mod go.sum ./\n\nRUN go mod download\n\nCOPY . ./\n\nCMD [\"make\", \"test\"]\n"
  },
  {
    "path": "Dockerfile.github-actions",
    "content": "FROM alpine:3.19\n\nRUN apk add --no-cache ca-certificates\n\nCOPY migrate /usr/local/bin/migrate\n\nRUN ln -s /usr/local/bin/migrate /usr/bin/migrate\nRUN ln -s /usr/local/bin/migrate /migrate\n\nENTRYPOINT [\"migrate\"]\nCMD [\"--help\"]\n"
  },
  {
    "path": "FAQ.md",
    "content": "# FAQ\n\n#### How is the code base structured?\n  ```\n  /          package migrate (the heart of everything)\n  /cli       the CLI wrapper\n  /database  database driver and sub directories have the actual driver implementations\n  /source    source driver and sub directories have the actual driver implementations\n  ```\n\n#### Why is there no `source/driver.go:Last()`?\n  It's not needed. And unless the source has a \"native\" way to read a directory in reversed order,\n  it might be expensive to do a full directory scan in order to get the last element.\n\n#### What is a NilMigration? NilVersion?\n  NilMigration defines a migration without a body. NilVersion is defined as const -1.\n\n#### What is the difference between uint(version) and int(targetVersion)?\n  version refers to an existing migration version coming from a source and therefore can never be negative.\n  targetVersion can either be a version OR represent a NilVersion, which equals -1.\n\n#### What's the difference between Next/Previous and Up/Down?\n  ```\n  1_first_migration.up.extension           next ->  2_second_migration.up.extension      ...\n  1_first_migration.down.extension  <- previous     2_second_migration.down.extension    ...\n  ```\n\n#### Why two separate files (up and down) for a migration?\n  It makes all of our lives easier. No new markup/syntax to learn for users\n  and existing database utility tools continue to work as expected.\n\n#### How many migrations can migrate handle?\n  Whatever the maximum positive signed integer value is for your platform.\n  For 32bit it would be 2,147,483,647 migrations. Migrate only keeps references to\n  the currently run and pre-fetched migrations in memory. Please note that some\n  source drivers need to do build a full \"directory\" tree first, which puts some\n  heat on the memory consumption.\n\n#### Are the table tests in migrate_test.go bloated?\n  Yes and no. There are duplicate test cases for sure but they don't hurt here. In fact\n  the tests are very visual now and might help new users understand expected behaviors quickly.\n  Migrate from version x to y and y is the last migration? Just check out the test for\n  that particular case and know what's going on instantly.\n\n#### What is Docker being used for?\n  Only for testing. See [testing/docker.go](testing/docker.go)\n\n#### Why not just use docker-compose?\n  It doesn't give us enough runtime control for testing. We want to be able to bring up containers fast\n  and whenever we want, not just once at the beginning of all tests.\n\n#### Can I maintain my driver in my own repository?\n  Yes, technically thats possible. We want to encourage you to contribute your driver to this repository though.\n  The driver's functionality is dictated by migrate's interfaces. That means there should really\n  just be one driver for a database/ source. We want to prevent a future where several drivers doing the exact same thing,\n  just implemented a bit differently, co-exist somewhere on GitHub. If users have to do research first to find the\n  \"best\" available driver for a database in order to get started, we would have failed as an open source community.\n\n#### Can I mix multiple sources during a batch of migrations?\n  No.\n\n#### What does \"dirty\" database mean?\n  Before a migration runs, each database sets a dirty flag. Execution stops if a migration fails and the dirty state persists,\n  which prevents attempts to run more migrations on top of a failed migration. You need to manually fix the error\n  and then \"force\" the expected version.\n\n#### What happens if two programs try and update the database at the same time?\n  Database-specific locking features are used by *some* database drivers to prevent multiple instances of migrate from running migrations on\n  the same database at the same time. For example, the MySQL driver uses the `GET_LOCK` function, while the Postgres driver uses\n  the `pg_advisory_lock` function.\n\n#### Do I need to create a table for tracking migration version used?\nNo, it is done automatically.\n\n#### Can I use migrate with a non-Go project?\nYes, you can use the migrate CLI in a non-Go project, but there are probably other libraries/frameworks available that offer better test and deploy integrations in that language/framework.\n\n#### I have got an error `Dirty database version 1. Fix and force version`. What should I do?\nKeep calm and refer to [the getting started docs](GETTING_STARTED.md#forcing-your-database-version).\n"
  },
  {
    "path": "GETTING_STARTED.md",
    "content": "# Getting started\nBefore you start, you should understand the concept of forward/up and reverse/down database migrations.\n\nConfigure a database for your application. Make sure that your database driver is supported [here](README.md#databases).\n\n## Create migrations\nCreate some migrations using migrate CLI. Here is an example:\n```\nmigrate create -ext sql -dir db/migrations -seq create_users_table\n```\nOnce you create your files, you should fill them.\n\n**IMPORTANT:** In a project developed by more than one person there is a chance of migrations inconsistency - e.g. two developers can create conflicting migrations, and the developer that created their migration later gets it merged to the repository first.\nDevelopers and Teams should keep an eye on such cases (especially during code review).\n[Here](https://github.com/golang-migrate/migrate/issues/179#issuecomment-475821264) is the issue summary if you would like to read more.\n\nConsider making your migrations idempotent - we can run the same sql code twice in a row with the same result. This makes our migrations more robust. On the other hand, it causes slightly less control over database schema - e.g. let's say you forgot to drop the table in down migration. You run down migration - the table is still there. When you run up migration again - `CREATE TABLE` would return an error, helping you find an issue in down migration, while `CREATE TABLE IF NOT EXISTS` would not. Use those conditions wisely.\n\nIn case you would like to run several commands/queries in one migration, you should wrap them in a transaction (if your database supports it).\nThis way if one of commands fails, our database will remain unchanged.\n\n## Run migrations\nRun your migrations through the CLI or your app and check if they applied expected changes.\nJust to give you an idea:\n```\nmigrate -database YOUR_DATABASE_URL -path PATH_TO_YOUR_MIGRATIONS up\n```\n\nJust add the code to your app and you're ready to go!\n\nBefore committing your migrations you should run your migrations up, down, and then up again to see if migrations are working properly both ways.\n(e.g. if you created a table in a migration but reverse migration did not delete it, you will encounter an error when running the forward migration again)\nIt's also worth checking your migrations in a separate, containerized environment. You can find some tools at the [end of this document](#further-reading).\n\n**IMPORTANT:** If you would like to run multiple instances of your app on different machines be sure to use a database that supports locking when running migrations. Otherwise you may encounter issues.\n\n## Forcing your database version\nIn case you run a migration that contained an error, migrate will not let you run other migrations on the same database. You will see an error like `Dirty database version 1. Fix and force version`, even when you fix the erred migration. This means your database was marked as 'dirty'.\nYou need to investigate the migration error - was your migration applied partially, or was it not applied at all? Once you know, you should force your database to a version reflecting it's real state. You can do so with `force` command:\n```\nmigrate -path PATH_TO_YOUR_MIGRATIONS -database YOUR_DATABASE_URL force VERSION\n```\nOnce you force the version and your migration was fixed, your database is 'clean' again and you can proceed with your migrations.\n\nFor details and example of usage see [this comment](https://github.com/golang-migrate/migrate/issues/282#issuecomment-530743258).\n\n## Further reading:\n- [PostgreSQL tutorial](database/postgres/TUTORIAL.md)\n- [Best practices](MIGRATIONS.md)\n- [FAQ](FAQ.md)\n- Tools for testing your migrations in a container:\n\t- https://github.com/dhui/dktest\n\t- https://github.com/ory/dockertest\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nOriginal Work\nCopyright (c) 2016 Matthias Kadenbach\nhttps://github.com/mattes/migrate\n\nModified Work\nCopyright (c) 2018 Dale Hui\nhttps://github.com/golang-migrate/migrate\n\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "MIGRATIONS.md",
    "content": "# Migrations\n\n## Migration Filename Format\n\nA single logical migration is represented as two separate migration files, one\nto migrate \"up\" to the specified version from the previous version, and a second\nto migrate back \"down\" to the previous version.  These migrations can be provided\nby any one of the supported [migration sources](./README.md#migration-sources).\n\nThe ordering and direction of the migration files is determined by the filenames\nused for them.  `migrate` expects the filenames of migrations to have the format:\n\n    {version}_{title}.up.{extension}\n    {version}_{title}.down.{extension}\n\nThe `title` of each migration is unused, and is only for readability.  Similarly,\nthe `extension` of the migration files is not checked by the library, and should\nbe an appropriate format for the database in use (`.sql` for SQL variants, for\ninstance).\n\nVersions of migrations may be represented as any 64 bit unsigned integer.\nAll migrations are applied upward in order of increasing version number, and\ndownward by decreasing version number.\n\nCommon versioning schemes include incrementing integers:\n\n    1_initialize_schema.down.sql\n    1_initialize_schema.up.sql\n    2_add_table.down.sql\n    2_add_table.up.sql\n    ...\n\nOr timestamps at an appropriate resolution:\n\n    1500360784_initialize_schema.down.sql\n    1500360784_initialize_schema.up.sql\n    1500445949_add_table.down.sql\n    1500445949_add_table.up.sql\n    ...\n\nBut any scheme resulting in distinct, incrementing integers as versions is valid.\n\nIt is suggested that the version number of corresponding `up` and `down` migration\nfiles be equivalent for clarity, but they are allowed to differ so long as the\nrelative ordering of the migrations is preserved.\n\nThe migration files are permitted to be \"empty\", in the event that a migration\nis a no-op or is irreversible. It is recommended to still include both migration\nfiles by making the whole migration file consist of a comment.\nIf your database does not support comments, then deleting the migration file will also work.\nNote, an actual empty file (e.g. a 0 byte file) may cause issues with your database since migrate\nwill attempt to run an empty query. In this case, deleting the migration file will also work.\nFor the rational of this behavior see:\n[#244 (comment)](https://github.com/golang-migrate/migrate/issues/244#issuecomment-510758270)\n\n## Migration Content Format\n\nThe format of the migration files themselves varies between database systems.\nDifferent databases have different semantics around schema changes and when and\nhow they are allowed to occur\n(for instance, [if schema changes can occur within a transaction](https://wiki.postgresql.org/wiki/Transactional_DDL_in_PostgreSQL:_A_Competitive_Analysis)).\n\nAs such, the `migrate` library has little to no checking around the format of\nmigration sources.  The migration files are generally processed directly by the\ndrivers as raw operations.\n\n## Reversibility of Migrations\n\nBest practice for writing schema migration is that all migrations should be\nreversible.  It should in theory be possible for run migrations down and back up\nthrough any and all versions with the state being fully cleaned and recreated\nby doing so.\n\nBy adhering to this recommended practice, development and deployment of new code\nis cleaner and easier (cleaning database state for a new feature should be as\neasy as migrating down to a prior version, and back up to the latest).\n\nAs opposed to some other migration libraries, `migrate` represents up and down\nmigrations as separate files.  This prevents any non-standard file syntax from\nbeing introduced which may result in unintended behavior or errors, depending\non what database is processing the file.\n\nWhile it is technically possible for an up or down migration to exist on its own\nwithout an equivalently versioned counterpart, it is strongly recommended to\nalways include a down migration which cleans up the state of the corresponding\nup migration.\n"
  },
  {
    "path": "Makefile",
    "content": "SOURCE ?= file go_bindata github github_ee bitbucket aws_s3 google_cloud_storage godoc_vfs gitlab\nDATABASE ?= postgres mysql redshift cassandra spanner cockroachdb yugabytedb clickhouse mongodb sqlserver firebird neo4j pgx pgx5 rqlite\nDATABASE_TEST ?= $(DATABASE) sqlite sqlite3 sqlcipher\nVERSION ?= $(shell git describe --tags 2>/dev/null | cut -c 2-)\nTEST_FLAGS ?=\nREPO_OWNER ?= $(shell cd .. && basename \"$$(pwd)\")\nCOVERAGE_DIR ?= .coverage\n\nbuild:\n\tCGO_ENABLED=0 go build -ldflags='-X main.Version=$(VERSION)' -tags '$(DATABASE) $(SOURCE)' ./cmd/migrate\n\nbuild-docker:\n\tCGO_ENABLED=0 go build -a -o build/migrate.linux-386 -ldflags=\"-s -w -X main.Version=${VERSION}\" -tags \"$(DATABASE) $(SOURCE)\" ./cmd/migrate\n\nbuild-cli: clean\n\t-mkdir ./cli/build\n\tcd ./cmd/migrate && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o ../../cli/build/migrate.linux-amd64 -ldflags='-X main.Version=$(VERSION) -extldflags \"-static\"' -tags '$(DATABASE) $(SOURCE)' .\n\tcd ./cmd/migrate && CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -a -o ../../cli/build/migrate.linux-armv7 -ldflags='-X main.Version=$(VERSION) -extldflags \"-static\"' -tags '$(DATABASE) $(SOURCE)' .\n\tcd ./cmd/migrate && CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -a -o ../../cli/build/migrate.linux-arm64 -ldflags='-X main.Version=$(VERSION) -extldflags \"-static\"' -tags '$(DATABASE) $(SOURCE)' .\n\tcd ./cmd/migrate && CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -a -o ../../cli/build/migrate.darwin-amd64 -ldflags='-X main.Version=$(VERSION) -extldflags \"-static\"' -tags '$(DATABASE) $(SOURCE)' .\n\tcd ./cmd/migrate && CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -a -o ../../cli/build/migrate.windows-386.exe -ldflags='-X main.Version=$(VERSION) -extldflags \"-static\"' -tags '$(DATABASE) $(SOURCE)' .\n\tcd ./cmd/migrate && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -a -o ../../cli/build/migrate.windows-amd64.exe -ldflags='-X main.Version=$(VERSION) -extldflags \"-static\"' -tags '$(DATABASE) $(SOURCE)' .\n\tcd ./cli/build && find . -name 'migrate*' | xargs -I{} tar czf {}.tar.gz {}\n\tcd ./cli/build && shasum -a 256 * > sha256sum.txt\n\tcat ./cli/build/sha256sum.txt\n\n\nclean:\n\t-rm -r ./cli/build\n\n\ntest-short:\n\tmake test-with-flags --ignore-errors TEST_FLAGS='-short'\n\n\ntest:\n\t@-rm -r $(COVERAGE_DIR)\n\t@mkdir $(COVERAGE_DIR)\n\tmake test-with-flags TEST_FLAGS='-v -race -covermode atomic -coverprofile $$(COVERAGE_DIR)/combined.txt -bench=. -benchmem -timeout 20m'\n\n\ntest-with-flags:\n\t@echo SOURCE: $(SOURCE)\n\t@echo DATABASE_TEST: $(DATABASE_TEST)\n\n\t@go test $(TEST_FLAGS) ./...\n\n\nkill-orphaned-docker-containers:\n\tdocker rm -f $(shell docker ps -aq --filter label=migrate_test)\n\n\nhtml-coverage:\n\tgo tool cover -html=$(COVERAGE_DIR)/combined.txt\n\n\nlist-external-deps:\n\t$(call external_deps,'.')\n\t$(call external_deps,'./cli/...')\n\t$(call external_deps,'./testing/...')\n\n\t$(foreach v, $(SOURCE), $(call external_deps,'./source/$(v)/...'))\n\t$(call external_deps,'./source/testing/...')\n\t$(call external_deps,'./source/stub/...')\n\n\t$(foreach v, $(DATABASE), $(call external_deps,'./database/$(v)/...'))\n\t$(call external_deps,'./database/testing/...')\n\t$(call external_deps,'./database/stub/...')\n\n\nrestore-import-paths:\n\tfind . -name '*.go' -type f -execdir sed -i '' s%\\\"github.com/$(REPO_OWNER)/migrate%\\\"github.com/mattes/migrate%g '{}' \\;\n\n\nrewrite-import-paths:\n\tfind . -name '*.go' -type f -execdir sed -i '' s%\\\"github.com/mattes/migrate%\\\"github.com/$(REPO_OWNER)/migrate%g '{}' \\;\n\n\n# example: fswatch -0 --exclude .godoc.pid --event Updated . | xargs -0 -n1 -I{} make docs\ndocs:\n\t-make kill-docs\n\tnohup godoc -play -http=127.0.0.1:6064 </dev/null >/dev/null 2>&1 & echo $$! > .godoc.pid\n\tcat .godoc.pid\n\n\nkill-docs:\n\t@cat .godoc.pid\n\tkill -9 $$(cat .godoc.pid)\n\trm .godoc.pid\n\n\nopen-docs:\n\topen http://localhost:6064/pkg/github.com/$(REPO_OWNER)/migrate\n\n\n# example: make release V=0.0.0\nrelease:\n\tgit tag v$(V)\n\t@read -p \"Press enter to confirm and push to origin ...\" && git push origin v$(V)\n\necho-source:\n\t@echo \"$(SOURCE)\"\n\necho-database:\n\t@echo \"$(DATABASE)\"\n\n\ndefine external_deps\n\t@echo '-- $(1)';  go list -f '{{join .Deps \"\\n\"}}' $(1) | grep -v github.com/$(REPO_OWNER)/migrate | xargs go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}'\n\nendef\n\n\n.PHONY: build build-docker build-cli clean test-short test test-with-flags html-coverage \\\n        restore-import-paths rewrite-import-paths list-external-deps release \\\n\t\tdocs kill-docs open-docs kill-orphaned-docker-containers echo-source echo-database\n\nSHELL = /bin/sh\nRAND = $(shell echo $$RANDOM)\n\n"
  },
  {
    "path": "README.md",
    "content": "[![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/golang-migrate/migrate/ci.yaml?branch=master)](https://github.com/golang-migrate/migrate/actions/workflows/ci.yaml?query=branch%3Amaster)\n[![GoDoc](https://pkg.go.dev/badge/github.com/golang-migrate/migrate)](https://pkg.go.dev/github.com/golang-migrate/migrate/v4)\n[![Coverage Status](https://img.shields.io/coveralls/github/golang-migrate/migrate/master.svg)](https://coveralls.io/github/golang-migrate/migrate?branch=master)\n[![packagecloud.io](https://img.shields.io/badge/deb-packagecloud.io-844fec.svg)](https://packagecloud.io/golang-migrate/migrate?filter=debs)\n[![Docker Pulls](https://img.shields.io/docker/pulls/migrate/migrate.svg)](https://hub.docker.com/r/migrate/migrate/)\n![Supported Go Versions](https://img.shields.io/badge/Go-1.24%2C%201.25-lightgrey.svg)\n[![GitHub Release](https://img.shields.io/github/release/golang-migrate/migrate.svg)](https://github.com/golang-migrate/migrate/releases)\n[![Go Report Card](https://goreportcard.com/badge/github.com/golang-migrate/migrate/v4)](https://goreportcard.com/report/github.com/golang-migrate/migrate/v4)\n\n# migrate\n\n__Database migrations written in Go. Use as [CLI](#cli-usage) or import as [library](#use-in-your-go-project).__\n\n* Migrate reads migrations from [sources](#migration-sources)\n   and applies them in correct order to a [database](#databases).\n* Drivers are \"dumb\", migrate glues everything together and makes sure the logic is bulletproof.\n   (Keeps the drivers lightweight, too.)\n* Database drivers don't assume things or try to correct user input. When in doubt, fail.\n\nForked from [mattes/migrate](https://github.com/mattes/migrate)\n\n## Databases\n\nDatabase drivers run migrations. [Add a new database?](database/driver.go)\n\n* [PostgreSQL](database/postgres)\n* [PGX v4](database/pgx)\n* [PGX v5](database/pgx/v5)\n* [Redshift](database/redshift)\n* [Ql](database/ql)\n* [Cassandra / ScyllaDB](database/cassandra)\n* [SQLite](database/sqlite)\n* [SQLite3](database/sqlite3) ([todo #165](https://github.com/mattes/migrate/issues/165))\n* [SQLCipher](database/sqlcipher)\n* [MySQL / MariaDB](database/mysql)\n* [Neo4j](database/neo4j)\n* [MongoDB](database/mongodb)\n* [CrateDB](database/crate) ([todo #170](https://github.com/mattes/migrate/issues/170))\n* [Shell](database/shell) ([todo #171](https://github.com/mattes/migrate/issues/171))\n* [Google Cloud Spanner](database/spanner)\n* [CockroachDB](database/cockroachdb)\n* [YugabyteDB](database/yugabytedb)\n* [ClickHouse](database/clickhouse)\n* [Firebird](database/firebird)\n* [MS SQL Server](database/sqlserver)\n* [rqlite](database/rqlite)\n\n### Database URLs\n\nDatabase connection strings are specified via URLs. The URL format is driver dependent but generally has the form: `dbdriver://username:password@host:port/dbname?param1=true&param2=false`\n\nAny [reserved URL characters](https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_reserved_characters) need to be escaped. Note, the `%` character also [needs to be escaped](https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_the_percent_character)\n\nExplicitly, the following characters need to be escaped:\n`!`, `#`, `$`, `%`, `&`, `'`, `(`, `)`, `*`, `+`, `,`, `/`, `:`, `;`, `=`, `?`, `@`, `[`, `]`\n\nIt's easiest to always run the URL parts of your DB connection URL (e.g. username, password, etc) through an URL encoder. See the example Python snippets below:\n\n```bash\n$ python3 -c 'import urllib.parse; print(urllib.parse.quote(input(\"String to encode: \"), \"\"))'\nString to encode: FAKEpassword!#$%&'()*+,/:;=?@[]\nFAKEpassword%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D\n$ python2 -c 'import urllib; print urllib.quote(raw_input(\"String to encode: \"), \"\")'\nString to encode: FAKEpassword!#$%&'()*+,/:;=?@[]\nFAKEpassword%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D\n$\n```\n\n## Migration Sources\n\nSource drivers read migrations from local or remote sources. [Add a new source?](source/driver.go)\n\n* [Filesystem](source/file) - read from filesystem\n* [io/fs](source/iofs) - read from a Go [io/fs](https://pkg.go.dev/io/fs#FS)\n* [Go-Bindata](source/go_bindata) - read from embedded binary data ([jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata))\n* [pkger](source/pkger) - read from embedded binary data ([markbates/pkger](https://github.com/markbates/pkger))\n* [GitHub](source/github) - read from remote GitHub repositories\n* [GitHub Enterprise](source/github_ee) - read from remote GitHub Enterprise repositories\n* [Bitbucket](source/bitbucket) - read from remote Bitbucket repositories\n* [Gitlab](source/gitlab) - read from remote Gitlab repositories\n* [AWS S3](source/aws_s3) - read from Amazon Web Services S3\n* [Google Cloud Storage](source/google_cloud_storage) - read from Google Cloud Platform Storage\n\n## CLI usage\n\n* Simple wrapper around this library.\n* Handles ctrl+c (SIGINT) gracefully.\n* No config search paths, no config files, no magic ENV var injections.\n\n[CLI Documentation](cmd/migrate) (includes CLI install instructions)\n\n### Basic usage\n\n```bash\n$ migrate -source file://path/to/migrations -database postgres://localhost:5432/database up 2\n```\n\n### Docker usage\n\n```bash\n$ docker run -v {{ migration dir }}:/migrations --network host migrate/migrate\n    -path=/migrations/ -database postgres://localhost:5432/database up 2\n```\n\n## Use in your Go project\n\n* API is stable and frozen for this release (v3 & v4).\n* Uses [Go modules](https://golang.org/cmd/go/#hdr-Modules__module_versions__and_more) to manage dependencies.\n* To help prevent database corruptions, it supports graceful stops via `GracefulStop chan bool`.\n* Bring your own logger.\n* Uses `io.Reader` streams internally for low memory overhead.\n* Thread-safe and no goroutine leaks.\n\n__[Go Documentation](https://pkg.go.dev/github.com/golang-migrate/migrate/v4)__\n\n```go\nimport (\n    \"github.com/golang-migrate/migrate/v4\"\n    _ \"github.com/golang-migrate/migrate/v4/database/postgres\"\n    _ \"github.com/golang-migrate/migrate/v4/source/github\"\n)\n\nfunc main() {\n    m, err := migrate.New(\n        \"github://mattes:personal-access-token@mattes/migrate_test\",\n        \"postgres://localhost:5432/database?sslmode=enable\")\n    m.Steps(2)\n}\n```\n\nWant to use an existing database client?\n\n```go\nimport (\n    \"database/sql\"\n    _ \"github.com/lib/pq\"\n    \"github.com/golang-migrate/migrate/v4\"\n    \"github.com/golang-migrate/migrate/v4/database/postgres\"\n    _ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nfunc main() {\n    db, err := sql.Open(\"postgres\", \"postgres://localhost:5432/database?sslmode=enable\")\n    driver, err := postgres.WithInstance(db, &postgres.Config{})\n    m, err := migrate.NewWithDatabaseInstance(\n        \"file:///migrations\",\n        \"postgres\", driver)\n    m.Up() // or m.Steps(2) if you want to explicitly set the number of migrations to run\n}\n```\n\n## Getting started\n\nGo to [getting started](GETTING_STARTED.md)\n\n## Tutorials\n\n* [CockroachDB](database/cockroachdb/TUTORIAL.md)\n* [PostgreSQL](database/postgres/TUTORIAL.md)\n\n(more tutorials to come)\n\n## Migration files\n\nEach migration has an up and down migration. [Why?](FAQ.md#why-two-separate-files-up-and-down-for-a-migration)\n\n```bash\n1481574547_create_users_table.up.sql\n1481574547_create_users_table.down.sql\n```\n\n[Best practices: How to write migrations.](MIGRATIONS.md)\n\n## Coming from another db migration tool?\n\nCheck out [migradaptor](https://github.com/musinit/migradaptor/).\n*Note: migradaptor is not affiliated or supported by this project*\n\n## Versions\n\nVersion | Supported? | Import | Notes\n--------|------------|--------|------\n**master** | :white_check_mark: | `import \"github.com/golang-migrate/migrate/v4\"` | New features and bug fixes arrive here first |\n**v4** | :white_check_mark: | `import \"github.com/golang-migrate/migrate/v4\"` | Used for stable releases |\n**v3** | :x: | `import \"github.com/golang-migrate/migrate\"` (with package manager) or `import \"gopkg.in/golang-migrate/migrate.v3\"` (not recommended) | **DO NOT USE** - No longer supported |\n\n## Development and Contributing\n\nYes, please! [`Makefile`](Makefile) is your friend,\nread the [development guide](CONTRIBUTING.md).\n\nAlso have a look at the [FAQ](FAQ.md).\n\n---\n\nLooking for alternatives? [https://awesome-go.com/#database](https://awesome-go.com/#database).\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported          |\n| ------- | ------------------ |\n| master  | :white_check_mark: |\n| 4.x     | :white_check_mark: |\n| 3.x     | :x:                |\n| < 3.0   | :x:                |\n\n## Reporting a Vulnerability\n\nWe prefer [coordinated disclosures](https://en.wikipedia.org/wiki/Coordinated_vulnerability_disclosure). To start one, create a GitHub security advisory following [these instructions](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability)\n\nPlease suggest potential impact and urgency in your reports.\n"
  },
  {
    "path": "cli/README.md",
    "content": "# Deprecated\n\nUse [cmd/migrate](../cmd/migrate) instead\n"
  },
  {
    "path": "cli/main.go",
    "content": "package main\n\nimport \"github.com/golang-migrate/migrate/v4/internal/cli\"\n\n// Deprecated, please use cmd/migrate\nfunc main() {\n\tcli.Main(Version)\n}\n"
  },
  {
    "path": "cli/version.go",
    "content": "package main\n\n// Version is set in Makefile with build flags\nvar Version = \"dev\"\n"
  },
  {
    "path": "cmd/migrate/README.md",
    "content": "# migrate CLI\n\n## Installation\n\n### Download pre-built binary (Windows, MacOS, or Linux)\n\n[Release Downloads](https://github.com/golang-migrate/migrate/releases)\n\n```bash\n$ curl -L https://github.com/golang-migrate/migrate/releases/download/$version/migrate.$os-$arch.tar.gz | tar xvz\n```\n\n### MacOS\n\n```bash\n$ brew install golang-migrate\n```\n\n### Windows\n\nUsing [scoop](https://scoop.sh/)\n\n```bash\n$ scoop install migrate\n```\n\n### Linux (*.deb package)\n\n```bash\n$ curl -fsSL https://packagecloud.io/golang-migrate/migrate/gpgkey | sudo gpg --dearmor -o /etc/apt/keyrings/migrate.gpg\n$ echo \"deb [signed-by=/etc/apt/keyrings/migrate.gpg] https://packagecloud.io/golang-migrate/migrate/ubuntu/ $(lsb_release -sc) main\" > /etc/apt/sources.list.d/migrate.list\n$ apt-get update\n$ apt-get install -y migrate\n```\n\n### With Go toolchain\n\n#### Versioned\n\n```bash\n$ go get -u -d github.com/golang-migrate/migrate/cmd/migrate\n$ cd $GOPATH/src/github.com/golang-migrate/migrate/cmd/migrate\n$ git checkout $TAG  # e.g. v4.1.0\n$ # Go 1.15 and below\n$ go build -tags 'postgres' -ldflags=\"-X main.Version=$(git describe --tags)\" -o $GOPATH/bin/migrate $GOPATH/src/github.com/golang-migrate/migrate/cmd/migrate\n$ # Go 1.16+\n$ go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@$TAG\n```\n\n#### Unversioned\n\n```bash\n$ # Go 1.15 and below\n$ go get -tags 'postgres' -u github.com/golang-migrate/migrate/cmd/migrate\n$ # Go 1.16+\n$ go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest\n```\n\n#### Notes\n\n1. Requires a version of Go that [supports modules](https://golang.org/cmd/go/#hdr-Preliminary_module_support). e.g. Go 1.11+\n1. These examples build the cli which will only work with postgres.  In order\nto build the cli for use with other databases, replace the `postgres` build tag\nwith the appropriate database tag(s) for the databases desired.  The tags\ncorrespond to the names of the sub-packages underneath the\n[`database`](../../database) package.\n1. Similarly to the database build tags, if you need to support other sources, use the appropriate build tag(s).\n1. Support for build constraints will be removed in the future: https://github.com/golang-migrate/migrate/issues/60\n1. For versions of Go 1.15 and lower, [make sure](https://github.com/golang-migrate/migrate/pull/257#issuecomment-705249902) you're not installing the `migrate` CLI from a module. e.g. there should not be any `go.mod` files in your current directory or any directory from your current directory to the root\n\n## Usage\n\n```bash\n$ migrate -help\nUsage: migrate OPTIONS COMMAND [arg...]\n       migrate [ -version | -help ]\n\nOptions:\n  -source          Location of the migrations (driver://url)\n  -path            Shorthand for -source=file://path\n  -database        Run migrations against this database (driver://url)\n  -prefetch N      Number of migrations to load in advance before executing (default 10)\n  -lock-timeout N  Allow N seconds to acquire database lock (default 15)\n  -verbose         Print verbose logging\n  -version         Print version\n  -help            Print usage\n\nCommands:\n  create [-ext E] [-dir D] [-seq] [-digits N] [-format] [-tz] NAME\n           Create a set of timestamped up/down migrations titled NAME, in directory D with extension E.\n           Use -seq option to generate sequential up/down migrations with N digits.\n           Use -format option to specify a Go time format string. Note: migrations with the same time cause \"duplicate migration version\" error.\n           Use -tz option to specify the timezone that will be used when generating non-sequential migrations (defaults: UTC).\n\n  goto V       Migrate to version V\n  up [N]       Apply all or N up migrations\n  down [N] [-all]    Apply all or N down migrations\n        Use -all to apply all down migrations\n  drop [-f]    Drop everything inside database\n        Use -f to bypass confirmation\n  force V      Set version V but don't run migration (ignores dirty state)\n  version      Print current migration version\n```\n\nSo let's say you want to run the first two migrations\n\n```bash\n$ migrate -source file://path/to/migrations -database postgres://localhost:5432/database up 2\n```\n\nIf your migrations are hosted on github\n\n```bash\n$ migrate -source github://mattes:personal-access-token@mattes/migrate_test \\\n    -database postgres://localhost:5432/database down 2\n```\n\nThe CLI will gracefully stop at a safe point when SIGINT (ctrl+c) is received.\nSend SIGKILL for immediate halt.\n\n## Reading CLI arguments from somewhere else\n\n### ENV variables\n\n```bash\n$ migrate -database \"$MY_MIGRATE_DATABASE\"\n```\n\n### JSON files\n\nCheck out https://stedolan.github.io/jq/\n\n```bash\n$ migrate -database \"$(cat config.json | jq -r '.database')\"\n```\n\n### YAML files\n\n```bash\n$ migrate -database \"$(cat config/database.yml | ruby -ryaml -e \"print YAML.load(STDIN.read)['database']\")\"\n$ migrate -database \"$(cat config/database.yml | python -c 'import yaml,sys;print yaml.safe_load(sys.stdin)[\"database\"]')\"\n```\n"
  },
  {
    "path": "cmd/migrate/examples/Dockerfile",
    "content": "FROM ubuntu:bionic\n\nRUN apt-get update && \\\n    apt-get install -y \\\n    apt-transport-https \\\n    ca-certificates \\\n    curl \\\n    gnupg-agent\n\nRUN curl -sSL https://packagecloud.io/golang-migrate/migrate/gpgkey | apt-key add -\nRUN echo \"deb https://packagecloud.io/golang-migrate/migrate/ubuntu/ bionic main\" > /etc/apt/sources.list.d/migrate.list\nRUN apt-get update && \\\n    apt-get install -y migrate\n\nRUN migrate -version\n"
  },
  {
    "path": "cmd/migrate/main.go",
    "content": "package main\n\nimport \"github.com/golang-migrate/migrate/v4/internal/cli\"\n\nfunc main() {\n\tcli.Main(Version)\n}\n"
  },
  {
    "path": "cmd/migrate/version.go",
    "content": "package main\n\n// Version is set in Makefile with build flags\nvar Version = \"dev\"\n"
  },
  {
    "path": "database/cassandra/README.md",
    "content": "# Cassandra / ScyllaDB\n\n* `Drop()` method will not work on Cassandra 2.X because it rely on\nsystem_schema table which comes with 3.X\n* Other methods should work properly but are **not tested**\n* The Cassandra driver (gocql) does not natively support executing multiple statements in a single query. To allow for multiple statements in a single migration, you can use the `x-multi-statement` param. There are two important caveats:\n  * This mode splits the migration text into separately-executed statements by a semi-colon `;`. Thus `x-multi-statement` cannot be used when a statement in the migration contains a string with a semi-colon.\n  * The queries are not executed in any sort of transaction/batch, meaning you are responsible for fixing partial migrations.\n\n**ScyllaDB**\n\n* No additional configuration is required since it is a drop-in replacement for Cassandra.\n* The `Drop()` method` works for ScyllaDB 5.1\n\n\n## Usage\n`cassandra://host:port/keyspace?param1=value&param2=value2`\n\n\n| URL Query  | Default value | Description |\n|------------|-------------|-----------|\n| `x-migrations-table` | schema_migrations | Name of the migrations table |\n| `x-multi-statement` | false | Enable multiple statements to be ran in a single migration (See note above) |\n| `port` | 9042 | The port to bind to  |\n| `consistency` | ALL | Migration consistency\n| `protocol` |  | Cassandra protocol version (3 or 4)\n| `timeout` | 1 minute | Migration timeout\n| `connect-timeout` | 600ms | Initial connection timeout to the cluster |\n| `username` | nil | Username to use when authenticating. |\n| `password` | nil | Password to use when authenticating. |\n| `sslcert` | | Cert file location. The file must contain PEM encoded data. |\n| `sslkey` | | Key file location. The file must contain PEM encoded data. |\n| `sslrootcert` | | The location of the root certificate file. The file must contain PEM encoded data. |\n| `sslmode` | | Whether or not to use SSL (disable\\|require\\|verify-ca\\|verify-full) |\n| `disable-host-lookup`| false | Disable initial host lookup. |\n\n`timeout` is parsed using [time.ParseDuration(s string)](https://golang.org/pkg/time/#ParseDuration)\n\n\n## Upgrading from v1\n\n1. Write down the current migration version from schema_migrations\n2. `DROP TABLE schema_migrations`\n4. Download and install the latest migrate version.\n5. Force the current migration version with `migrate force <current_version>`.\n"
  },
  {
    "path": "database/cassandra/cassandra.go",
    "content": "package cassandra\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/gocql/gocql\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t\"github.com/golang-migrate/migrate/v4/database/multistmt\"\n)\n\nfunc init() {\n\tdb := new(Cassandra)\n\tdatabase.Register(\"cassandra\", db)\n}\n\nvar (\n\tmultiStmtDelimiter = []byte(\";\")\n\n\tDefaultMultiStatementMaxSize = 10 * 1 << 20 // 10 MB\n)\n\nvar DefaultMigrationsTable = \"schema_migrations\"\n\nvar (\n\tErrNilConfig     = errors.New(\"no config\")\n\tErrNoKeyspace    = errors.New(\"no keyspace provided\")\n\tErrDatabaseDirty = errors.New(\"database is dirty\")\n\tErrClosedSession = errors.New(\"session is closed\")\n)\n\ntype Config struct {\n\tMigrationsTable       string\n\tKeyspaceName          string\n\tMultiStatementEnabled bool\n\tMultiStatementMaxSize int\n}\n\ntype Cassandra struct {\n\tsession  *gocql.Session\n\tisLocked atomic.Bool\n\n\t// Open and WithInstance need to guarantee that config is never nil\n\tconfig *Config\n}\n\nfunc WithInstance(session *gocql.Session, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t} else if len(config.KeyspaceName) == 0 {\n\t\treturn nil, ErrNoKeyspace\n\t}\n\n\tif session.Closed() {\n\t\treturn nil, ErrClosedSession\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tif config.MultiStatementMaxSize <= 0 {\n\t\tconfig.MultiStatementMaxSize = DefaultMultiStatementMaxSize\n\t}\n\n\tc := &Cassandra{\n\t\tsession: session,\n\t\tconfig:  config,\n\t}\n\n\tif err := c.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn c, nil\n}\n\nfunc (c *Cassandra) Open(url string) (database.Driver, error) {\n\tu, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Check for missing mandatory attributes\n\tif len(u.Path) == 0 {\n\t\treturn nil, ErrNoKeyspace\n\t}\n\n\tcluster := gocql.NewCluster(u.Host)\n\tcluster.Keyspace = strings.TrimPrefix(u.Path, \"/\")\n\tcluster.Consistency = gocql.All\n\tcluster.Timeout = 1 * time.Minute\n\n\tif len(u.Query().Get(\"username\")) > 0 && len(u.Query().Get(\"password\")) > 0 {\n\t\tauthenticator := gocql.PasswordAuthenticator{\n\t\t\tUsername: u.Query().Get(\"username\"),\n\t\t\tPassword: u.Query().Get(\"password\"),\n\t\t}\n\t\tcluster.Authenticator = authenticator\n\t}\n\n\t// Retrieve query string configuration\n\tif len(u.Query().Get(\"consistency\")) > 0 {\n\t\tvar consistency gocql.Consistency\n\t\tconsistency, err = parseConsistency(u.Query().Get(\"consistency\"))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcluster.Consistency = consistency\n\t}\n\tif len(u.Query().Get(\"protocol\")) > 0 {\n\t\tvar protoversion int\n\t\tprotoversion, err = strconv.Atoi(u.Query().Get(\"protocol\"))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcluster.ProtoVersion = protoversion\n\t}\n\tif len(u.Query().Get(\"timeout\")) > 0 {\n\t\tvar timeout time.Duration\n\t\ttimeout, err = time.ParseDuration(u.Query().Get(\"timeout\"))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcluster.Timeout = timeout\n\t}\n\tif len(u.Query().Get(\"connect-timeout\")) > 0 {\n\t\tvar connectTimeout time.Duration\n\t\tconnectTimeout, err = time.ParseDuration(u.Query().Get(\"connect-timeout\"))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcluster.ConnectTimeout = connectTimeout\n\t}\n\n\tif len(u.Query().Get(\"sslmode\")) > 0 {\n\t\tif u.Query().Get(\"sslmode\") != \"disable\" {\n\t\t\tsslOpts := &gocql.SslOptions{}\n\n\t\t\tif len(u.Query().Get(\"sslrootcert\")) > 0 {\n\t\t\t\tsslOpts.CaPath = u.Query().Get(\"sslrootcert\")\n\t\t\t}\n\t\t\tif len(u.Query().Get(\"sslcert\")) > 0 {\n\t\t\t\tsslOpts.CertPath = u.Query().Get(\"sslcert\")\n\t\t\t}\n\t\t\tif len(u.Query().Get(\"sslkey\")) > 0 {\n\t\t\t\tsslOpts.KeyPath = u.Query().Get(\"sslkey\")\n\t\t\t}\n\n\t\t\tif u.Query().Get(\"sslmode\") == \"verify-full\" {\n\t\t\t\tsslOpts.EnableHostVerification = true\n\t\t\t}\n\n\t\t\tcluster.SslOpts = sslOpts\n\t\t}\n\t}\n\n\tif len(u.Query().Get(\"disable-host-lookup\")) > 0 {\n\t\tif flag, err := strconv.ParseBool(u.Query().Get(\"disable-host-lookup\")); err != nil && flag {\n\t\t\tcluster.DisableInitialHostLookup = true\n\t\t} else if err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsession, err := cluster.CreateSession()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmultiStatementMaxSize := DefaultMultiStatementMaxSize\n\tif s := u.Query().Get(\"x-multi-statement-max-size\"); len(s) > 0 {\n\t\tmultiStatementMaxSize, err = strconv.Atoi(s)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn WithInstance(session, &Config{\n\t\tKeyspaceName:          strings.TrimPrefix(u.Path, \"/\"),\n\t\tMigrationsTable:       u.Query().Get(\"x-migrations-table\"),\n\t\tMultiStatementEnabled: u.Query().Get(\"x-multi-statement\") == \"true\",\n\t\tMultiStatementMaxSize: multiStatementMaxSize,\n\t})\n}\n\nfunc (c *Cassandra) Close() error {\n\tc.session.Close()\n\treturn nil\n}\n\nfunc (c *Cassandra) Lock() error {\n\tif !c.isLocked.CompareAndSwap(false, true) {\n\t\treturn database.ErrLocked\n\t}\n\treturn nil\n}\n\nfunc (c *Cassandra) Unlock() error {\n\tif !c.isLocked.CompareAndSwap(true, false) {\n\t\treturn database.ErrNotLocked\n\t}\n\treturn nil\n}\n\nfunc (c *Cassandra) Run(migration io.Reader) error {\n\tif c.config.MultiStatementEnabled {\n\t\tvar err error\n\t\tif e := multistmt.Parse(migration, multiStmtDelimiter, c.config.MultiStatementMaxSize, func(m []byte) bool {\n\t\t\ttq := strings.TrimSpace(string(m))\n\t\t\tif tq == \"\" {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif e := c.session.Query(tq).Exec(); e != nil {\n\t\t\t\terr = database.Error{OrigErr: e, Err: \"migration failed\", Query: m}\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t}); e != nil {\n\t\t\treturn e\n\t\t}\n\t\treturn err\n\t}\n\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// run migration\n\tif err := c.session.Query(string(migr)).Exec(); err != nil {\n\t\t// TODO: cast to Cassandra error and get line number\n\t\treturn database.Error{OrigErr: err, Err: \"migration failed\", Query: migr}\n\t}\n\treturn nil\n}\n\nfunc (c *Cassandra) SetVersion(version int, dirty bool) error {\n\t// DELETE instead of TRUNCATE because AWS Keyspaces does not support it\n\t// see: https://docs.aws.amazon.com/keyspaces/latest/devguide/cassandra-apis.html\n\tsquery := `SELECT version FROM \"` + c.config.MigrationsTable + `\"`\n\tdquery := `DELETE FROM \"` + c.config.MigrationsTable + `\" WHERE version = ?`\n\titer := c.session.Query(squery).Iter()\n\tvar previous int\n\tfor iter.Scan(&previous) {\n\t\tif err := c.session.Query(dquery, previous).Exec(); err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(dquery)}\n\t\t}\n\t}\n\tif err := iter.Close(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(squery)}\n\t}\n\n\t// Also re-write the schema version for nil dirty versions to prevent\n\t// empty schema version for failed down migration on the first migration\n\t// See: https://github.com/golang-migrate/migrate/issues/330\n\tif version >= 0 || (version == database.NilVersion && dirty) {\n\t\tquery := `INSERT INTO \"` + c.config.MigrationsTable + `\" (version, dirty) VALUES (?, ?)`\n\t\tif err := c.session.Query(query, version, dirty).Exec(); err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Return current keyspace version\nfunc (c *Cassandra) Version() (version int, dirty bool, err error) {\n\tquery := `SELECT version, dirty FROM \"` + c.config.MigrationsTable + `\" LIMIT 1`\n\terr = c.session.Query(query).Scan(&version, &dirty)\n\tswitch {\n\tcase err == gocql.ErrNotFound:\n\t\treturn database.NilVersion, false, nil\n\n\tcase err != nil:\n\t\tif _, ok := err.(*gocql.Error); ok {\n\t\t\treturn database.NilVersion, false, nil\n\t\t}\n\t\treturn 0, false, &database.Error{OrigErr: err, Query: []byte(query)}\n\n\tdefault:\n\t\treturn version, dirty, nil\n\t}\n}\n\nfunc (c *Cassandra) Drop() error {\n\t// select all tables in current schema\n\tquery := fmt.Sprintf(`SELECT table_name from system_schema.tables WHERE keyspace_name='%s'`, c.config.KeyspaceName)\n\titer := c.session.Query(query).Iter()\n\tvar tableName string\n\tfor iter.Scan(&tableName) {\n\t\terr := c.session.Query(fmt.Sprintf(`DROP TABLE %s`, tableName)).Exec()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database, which deviates from the usual\n// convention of \"caller locks\" in the Cassandra type.\nfunc (c *Cassandra) ensureVersionTable() (err error) {\n\tif err = c.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := c.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\terr = c.session.Query(fmt.Sprintf(\"CREATE TABLE IF NOT EXISTS %s (version bigint, dirty boolean, PRIMARY KEY(version))\", c.config.MigrationsTable)).Exec()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif _, _, err = c.Version(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// ParseConsistency wraps gocql.ParseConsistency\n// to return an error instead of a panicking.\nfunc parseConsistency(consistencyStr string) (consistency gocql.Consistency, err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tvar ok bool\n\t\t\terr, ok = r.(error)\n\t\t\tif !ok {\n\t\t\t\terr = fmt.Errorf(\"failed to parse consistency \\\"%s\\\": %v\", consistencyStr, r)\n\t\t\t}\n\t\t}\n\t}()\n\tconsistency = gocql.ParseConsistency(consistencyStr)\n\n\treturn consistency, nil\n}\n"
  },
  {
    "path": "database/cassandra/cassandra_test.go",
    "content": "package cassandra\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"strconv\"\n\t\"testing\"\n)\n\nimport (\n\t\"github.com/dhui/dktest\"\n\t\"github.com/gocql/gocql\"\n)\n\nimport (\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t\"github.com/golang-migrate/migrate/v4/dktesting\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nvar (\n\topts = dktest.Options{PortRequired: true, ReadyFunc: isReady}\n\t// Supported versions: http://cassandra.apache.org/download/\n\t// Although Cassandra 2.x is supported by the Apache Foundation,\n\t// the migrate db driver only supports Cassandra 3.x since it uses\n\t// the system_schema keyspace.\n\t// last ScyllaDB version tested is 5.1.11\n\tspecs = []dktesting.ContainerSpec{\n\t\t{ImageName: \"cassandra:3.0\", Options: opts},\n\t\t{ImageName: \"cassandra:3.11\", Options: opts},\n\t\t{ImageName: \"scylladb/scylla:5.1.11\", Options: opts},\n\t}\n)\n\nfunc isReady(ctx context.Context, c dktest.ContainerInfo) bool {\n\t// Cassandra exposes 5 ports (7000, 7001, 7199, 9042 & 9160)\n\t// We only need the port bound to 9042\n\tip, portStr, err := c.Port(9042)\n\tif err != nil {\n\t\treturn false\n\t}\n\tport, err := strconv.Atoi(portStr)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tcluster := gocql.NewCluster(ip)\n\tcluster.Port = port\n\tcluster.Consistency = gocql.All\n\tp, err := cluster.CreateSession()\n\tif err != nil {\n\t\treturn false\n\t}\n\tdefer p.Close()\n\t// Create keyspace for tests\n\tif err = p.Query(\"CREATE KEYSPACE testks WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor':1}\").Exec(); err != nil {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc Test(t *testing.T) {\n\tt.Run(\"test\", test)\n\tt.Run(\"testMigrate\", testMigrate)\n\n\tt.Cleanup(func() {\n\t\tfor _, spec := range specs {\n\t\t\tt.Log(\"Cleaning up \", spec.ImageName)\n\t\t\tif err := spec.Cleanup(); err != nil {\n\t\t\t\tt.Error(\"Error removing \", spec.ImageName, \"error:\", err)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc test(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(9042)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to get mapped port:\", err)\n\t\t}\n\t\taddr := fmt.Sprintf(\"cassandra://%v:%v/testks\", ip, port)\n\t\tp := &Cassandra{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tdt.Test(t, d, []byte(\"SELECT table_name from system_schema.tables\"))\n\t})\n}\n\nfunc testMigrate(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(9042)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to get mapped port:\", err)\n\t\t}\n\t\taddr := fmt.Sprintf(\"cassandra://%v:%v/testks\", ip, port)\n\t\tp := &Cassandra{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://./examples/migrations\", \"testks\", d)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\t})\n}\n"
  },
  {
    "path": "database/cassandra/examples/migrations/1_simple_select.down.sql",
    "content": "SELECT table_name from system_schema.tables"
  },
  {
    "path": "database/cassandra/examples/migrations/1_simple_select.up.sql",
    "content": "SELECT table_name from system_schema.tables"
  },
  {
    "path": "database/clickhouse/README.md",
    "content": "# ClickHouse\n\n`clickhouse://host:port?username=user&password=password&database=clicks&x-multi-statement=true`\n\n| URL Query  | Description |\n|------------|-------------|\n| `x-migrations-table`| Name of the migrations table |\n| `x-migrations-table-engine`| Engine to use for the migrations table, defaults to TinyLog |\n| `x-cluster-name` | Name of cluster for creating `schema_migrations` table cluster wide |\n| `database` | The name of the database to connect to |\n| `username` | The user to sign in as |\n| `password` | The user's password |\n| `host` | The host to connect to. |\n| `port` | The port to bind to. |\n| `x-multi-statement` | false | Enable multiple statements to be ran in a single migration (See note below) |\n\n## Notes\n\n* The Clickhouse driver does not natively support executing multiple statements in a single query. To allow for multiple statements in a single migration, you can use the `x-multi-statement` param. There are two important caveats:\n  * This mode splits the migration text into separately-executed statements by a semi-colon `;`. Thus `x-multi-statement` cannot be used when a statement in the migration contains a string with a semi-colon.\n  * The queries are not executed in any sort of transaction/batch, meaning you are responsible for fixing partial migrations.\n* Using the default TinyLog table engine for the schema_versions table prevents backing up the table if using the [clickhouse-backup](https://github.com/AlexAkulov/clickhouse-backup) tool. If backing up the database with make sure the migrations are run with `x-migrations-table-engine=MergeTree`.\n* Clickhouse cluster mode is not officially supported, since it's not tested right now, but you can try enabling `schema_migrations` table replication by specifying a `x-cluster-name`:\n  * When `x-cluster-name` is specified, `x-migrations-table-engine` also should be specified. See the docs regarding [replicated table engines](https://clickhouse.tech/docs/en/engines/table-engines/mergetree-family/replication/#table_engines-replication).\n  * When `x-cluster-name` is specified, only the `schema_migrations` table is replicated across the cluster. You still need to write your migrations so that the application tables are replicated within the cluster.\n* If you want to create database inside the migration, you should know, that table which will manage migrations `schema-migrations table` will be in `default` table, so you can't use `USE <database_name>` inside migration. In this case you may not specify the database in the connection string (example you can find [here](examples/migrations/003_create_database.up.sql))\n"
  },
  {
    "path": "database/clickhouse/clickhouse.go",
    "content": "package clickhouse\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t\"github.com/golang-migrate/migrate/v4/database/multistmt\"\n)\n\nvar (\n\tmultiStmtDelimiter = []byte(\";\")\n\n\tDefaultMigrationsTable       = \"schema_migrations\"\n\tDefaultMigrationsTableEngine = \"TinyLog\"\n\tDefaultMultiStatementMaxSize = 10 * 1 << 20 // 10 MB\n\n\tErrNilConfig = fmt.Errorf(\"no config\")\n)\n\ntype Config struct {\n\tDatabaseName          string\n\tClusterName           string\n\tMigrationsTable       string\n\tMigrationsTableEngine string\n\tMultiStatementEnabled bool\n\tMultiStatementMaxSize int\n}\n\nfunc init() {\n\tdatabase.Register(\"clickhouse\", &ClickHouse{})\n}\n\nfunc WithInstance(conn *sql.DB, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif err := conn.Ping(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tch := &ClickHouse{\n\t\tconn:   conn,\n\t\tconfig: config,\n\t}\n\n\tif err := ch.init(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ch, nil\n}\n\ntype ClickHouse struct {\n\tconn     *sql.DB\n\tconfig   *Config\n\tisLocked atomic.Bool\n}\n\nfunc (ch *ClickHouse) Open(dsn string) (database.Driver, error) {\n\tpurl, err := url.Parse(dsn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tq := migrate.FilterCustomQuery(purl)\n\tq.Scheme = \"tcp\"\n\tconn, err := sql.Open(\"clickhouse\", q.String())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmultiStatementMaxSize := DefaultMultiStatementMaxSize\n\tif s := purl.Query().Get(\"x-multi-statement-max-size\"); len(s) > 0 {\n\t\tmultiStatementMaxSize, err = strconv.Atoi(s)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tmigrationsTableEngine := DefaultMigrationsTableEngine\n\tif s := purl.Query().Get(\"x-migrations-table-engine\"); len(s) > 0 {\n\t\tmigrationsTableEngine = s\n\t}\n\n\tch = &ClickHouse{\n\t\tconn: conn,\n\t\tconfig: &Config{\n\t\t\tMigrationsTable:       purl.Query().Get(\"x-migrations-table\"),\n\t\t\tMigrationsTableEngine: migrationsTableEngine,\n\t\t\tDatabaseName:          purl.Query().Get(\"database\"),\n\t\t\tClusterName:           purl.Query().Get(\"x-cluster-name\"),\n\t\t\tMultiStatementEnabled: purl.Query().Get(\"x-multi-statement\") == \"true\",\n\t\t\tMultiStatementMaxSize: multiStatementMaxSize,\n\t\t},\n\t}\n\n\tif err := ch.init(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ch, nil\n}\n\nfunc (ch *ClickHouse) init() error {\n\tif len(ch.config.DatabaseName) == 0 {\n\t\tif err := ch.conn.QueryRow(\"SELECT currentDatabase()\").Scan(&ch.config.DatabaseName); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif len(ch.config.MigrationsTable) == 0 {\n\t\tch.config.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tif ch.config.MultiStatementMaxSize <= 0 {\n\t\tch.config.MultiStatementMaxSize = DefaultMultiStatementMaxSize\n\t}\n\n\tif len(ch.config.MigrationsTableEngine) == 0 {\n\t\tch.config.MigrationsTableEngine = DefaultMigrationsTableEngine\n\t}\n\n\treturn ch.ensureVersionTable()\n}\n\nfunc (ch *ClickHouse) Run(r io.Reader) error {\n\tif ch.config.MultiStatementEnabled {\n\t\tvar err error\n\t\tif e := multistmt.Parse(r, multiStmtDelimiter, ch.config.MultiStatementMaxSize, func(m []byte) bool {\n\t\t\ttq := strings.TrimSpace(string(m))\n\t\t\tif tq == \"\" {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif _, e := ch.conn.Exec(string(m)); e != nil {\n\t\t\t\terr = database.Error{OrigErr: e, Err: \"migration failed\", Query: m}\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t}); e != nil {\n\t\t\treturn e\n\t\t}\n\t\treturn err\n\t}\n\n\tmigration, err := io.ReadAll(r)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif _, err := ch.conn.Exec(string(migration)); err != nil {\n\t\treturn database.Error{OrigErr: err, Err: \"migration failed\", Query: migration}\n\t}\n\n\treturn nil\n}\nfunc (ch *ClickHouse) Version() (int, bool, error) {\n\tvar (\n\t\tversion int\n\t\tdirty   uint8\n\t\tquery   = \"SELECT version, dirty FROM `\" + ch.config.MigrationsTable + \"` ORDER BY sequence DESC LIMIT 1\"\n\t)\n\tif err := ch.conn.QueryRow(query).Scan(&version, &dirty); err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\treturn database.NilVersion, false, nil\n\t\t}\n\t\treturn 0, false, &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\treturn version, dirty == 1, nil\n}\n\nfunc (ch *ClickHouse) SetVersion(version int, dirty bool) error {\n\tvar (\n\t\tbool = func(v bool) uint8 {\n\t\t\tif v {\n\t\t\t\treturn 1\n\t\t\t}\n\t\t\treturn 0\n\t\t}\n\t\ttx, err = ch.conn.Begin()\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tquery := \"INSERT INTO \" + ch.config.MigrationsTable + \" (version, dirty, sequence) VALUES (?, ?, ?)\"\n\tif _, err := tx.Exec(query, version, bool(dirty), time.Now().UnixNano()); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn tx.Commit()\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database, which deviates from the usual\n// convention of \"caller locks\" in the ClickHouse type.\nfunc (ch *ClickHouse) ensureVersionTable() (err error) {\n\tif err = ch.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := ch.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\tvar (\n\t\ttable string\n\t\tquery = \"SHOW TABLES FROM \" + quoteIdentifier(ch.config.DatabaseName) + \" LIKE '\" + ch.config.MigrationsTable + \"'\"\n\t)\n\t// check if migration table exists\n\tif err := ch.conn.QueryRow(query).Scan(&table); err != nil {\n\t\tif err != sql.ErrNoRows {\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t} else {\n\t\treturn nil\n\t}\n\n\t// if not, create the empty migration table\n\tif len(ch.config.ClusterName) > 0 {\n\t\tquery = fmt.Sprintf(`\n\t\t\tCREATE TABLE %s ON CLUSTER %s (\n\t\t\t\tversion    Int64,\n\t\t\t\tdirty      UInt8,\n\t\t\t\tsequence   UInt64\n\t\t\t) Engine=%s`, ch.config.MigrationsTable, ch.config.ClusterName, ch.config.MigrationsTableEngine)\n\t} else {\n\t\tquery = fmt.Sprintf(`\n\t\t\tCREATE TABLE %s (\n\t\t\t\tversion    Int64,\n\t\t\t\tdirty      UInt8,\n\t\t\t\tsequence   UInt64\n\t\t\t) Engine=%s`, ch.config.MigrationsTable, ch.config.MigrationsTableEngine)\n\t}\n\n\tif strings.HasSuffix(ch.config.MigrationsTableEngine, \"Tree\") {\n\t\tquery = fmt.Sprintf(`%s ORDER BY sequence`, query)\n\t}\n\n\tif _, err := ch.conn.Exec(query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\treturn nil\n}\n\nfunc (ch *ClickHouse) Drop() (err error) {\n\tquery := \"SHOW TABLES FROM \" + quoteIdentifier(ch.config.DatabaseName)\n\ttables, err := ch.conn.Query(query)\n\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tdefer func() {\n\t\tif errClose := tables.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\tfor tables.Next() {\n\t\tvar table string\n\t\tif err := tables.Scan(&table); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tquery = \"DROP TABLE IF EXISTS \" + quoteIdentifier(ch.config.DatabaseName) + \".\" + quoteIdentifier(table)\n\n\t\tif _, err := ch.conn.Exec(query); err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\tif err := tables.Err(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn nil\n}\n\nfunc (ch *ClickHouse) Lock() error {\n\tif !ch.isLocked.CompareAndSwap(false, true) {\n\t\treturn database.ErrLocked\n\t}\n\n\treturn nil\n}\nfunc (ch *ClickHouse) Unlock() error {\n\tif !ch.isLocked.CompareAndSwap(true, false) {\n\t\treturn database.ErrNotLocked\n\t}\n\n\treturn nil\n}\nfunc (ch *ClickHouse) Close() error { return ch.conn.Close() }\n\n// Copied from lib/pq implementation: https://github.com/lib/pq/blob/v1.9.0/conn.go#L1611\nfunc quoteIdentifier(name string) string {\n\tend := strings.IndexRune(name, 0)\n\tif end > -1 {\n\t\tname = name[:end]\n\t}\n\treturn `\"` + strings.ReplaceAll(name, `\"`, `\"\"`) + `\"`\n}\n"
  },
  {
    "path": "database/clickhouse/clickhouse_test.go",
    "content": "package clickhouse_test\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\tsqldriver \"database/sql/driver\"\n\t\"fmt\"\n\t\"log\"\n\t\"testing\"\n\n\t_ \"github.com/ClickHouse/clickhouse-go\"\n\t\"github.com/dhui/dktest\"\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database/clickhouse\"\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t\"github.com/golang-migrate/migrate/v4/dktesting\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nconst defaultPort = 9000\n\nvar (\n\ttableEngines = []string{\"TinyLog\", \"MergeTree\"}\n\topts         = dktest.Options{\n\t\tEnv:          map[string]string{\"CLICKHOUSE_USER\": \"user\", \"CLICKHOUSE_PASSWORD\": \"password\", \"CLICKHOUSE_DB\": \"db\"},\n\t\tPortRequired: true, ReadyFunc: isReady,\n\t}\n\tspecs = []dktesting.ContainerSpec{\n\t\t{ImageName: \"clickhouse:24.8\", Options: opts},\n\t}\n)\n\nfunc clickhouseConnectionString(host, port, engine string) string {\n\tif engine != \"\" {\n\t\treturn fmt.Sprintf(\n\t\t\t\"clickhouse://%v:%v?username=user&password=password&database=db&x-multi-statement=true&x-migrations-table-engine=%v&debug=false\",\n\t\t\thost, port, engine)\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"clickhouse://%v:%v?username=user&password=password&database=db&x-multi-statement=true&debug=false\",\n\t\thost, port)\n}\n\nfunc isReady(ctx context.Context, c dktest.ContainerInfo) bool {\n\tip, port, err := c.Port(defaultPort)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tdb, err := sql.Open(\"clickhouse\", clickhouseConnectionString(ip, port, \"\"))\n\n\tif err != nil {\n\t\tlog.Println(\"open error\", err)\n\t\treturn false\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\tlog.Println(\"close error:\", err)\n\t\t}\n\t}()\n\n\tif err = db.PingContext(ctx); err != nil {\n\t\tswitch err {\n\t\tcase sqldriver.ErrBadConn:\n\t\t\treturn false\n\t\tdefault:\n\t\t\tfmt.Println(err)\n\t\t}\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc TestCases(t *testing.T) {\n\tfor _, engine := range tableEngines {\n\t\tt.Run(\"Test_\"+engine, func(t *testing.T) { testSimple(t, engine) })\n\t\tt.Run(\"Migrate_\"+engine, func(t *testing.T) { testMigrate(t, engine) })\n\t\tt.Run(\"Version_\"+engine, func(t *testing.T) { testVersion(t, engine) })\n\t\tt.Run(\"Drop_\"+engine, func(t *testing.T) { testDrop(t, engine) })\n\t}\n\tt.Run(\"WithInstanceDefaultConfigValues\", func(t *testing.T) { testSimpleWithInstanceDefaultConfigValues(t) })\n}\n\nfunc testSimple(t *testing.T, engine string) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := clickhouseConnectionString(ip, port, engine)\n\t\tp := &clickhouse.ClickHouse{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\t})\n}\n\nfunc testSimpleWithInstanceDefaultConfigValues(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := clickhouseConnectionString(ip, port, \"\")\n\t\tconn, err := sql.Open(\"clickhouse\", addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\td, err := clickhouse.WithInstance(conn, &clickhouse.Config{})\n\t\tif err != nil {\n\t\t\t_ = conn.Close()\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\t})\n}\n\nfunc testMigrate(t *testing.T, engine string) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := clickhouseConnectionString(ip, port, engine)\n\t\tp := &clickhouse.ClickHouse{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://./examples/migrations\", \"db\", d)\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\t})\n}\n\nfunc testVersion(t *testing.T, engine string) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\texpectedVersion := 1\n\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := clickhouseConnectionString(ip, port, engine)\n\t\tp := &clickhouse.ClickHouse{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\terr = d.SetVersion(expectedVersion, false)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tversion, _, err := d.Version()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif version != expectedVersion {\n\t\t\tt.Fatal(\"Version mismatch\")\n\t\t}\n\t})\n}\n\nfunc testDrop(t *testing.T, engine string) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := clickhouseConnectionString(ip, port, engine)\n\t\tp := &clickhouse.ClickHouse{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\terr = d.Drop()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "database/clickhouse/examples/migrations/001_init.down.sql",
    "content": "DROP TABLE IF EXISTS test_1;"
  },
  {
    "path": "database/clickhouse/examples/migrations/001_init.up.sql",
    "content": "CREATE TABLE test_1 (\n    Date Date \n) Engine=Memory;"
  },
  {
    "path": "database/clickhouse/examples/migrations/002_create_table.down.sql",
    "content": "DROP TABLE IF EXISTS test_2;"
  },
  {
    "path": "database/clickhouse/examples/migrations/002_create_table.up.sql",
    "content": "CREATE TABLE test_2 (\n    Date Date \n) Engine=Memory;"
  },
  {
    "path": "database/clickhouse/examples/migrations/003_create_database.down.sql",
    "content": "DROP TABLE IF EXISTS driver_ratings;\nDROP TABLE IF EXISTS user_ratings;\nDROP TABLE IF EXISTS orders;\nDROP TABLE IF EXISTS driver_ratings_queue;\nDROP TABLE IF EXISTS user_ratings_queue;\nDROP TABLE IF EXISTS orders_queue;\nDROP VIEW IF EXISTS user_ratings_queue_mv;\nDROP VIEW IF EXISTS driver_ratings_queue_mv;\nDROP VIEW IF EXISTS orders_queue_mv;\nDROP DATABASE IF EXISTS analytics;\n"
  },
  {
    "path": "database/clickhouse/examples/migrations/003_create_database.up.sql",
    "content": "CREATE DATABASE IF NOT EXISTS analytics;\n\nCREATE TABLE IF NOT EXISTS analytics.driver_ratings(\n    rate UInt8,\n    userID Int64,\n    driverID String,\n    orderID String,\n    inserted_time DateTime DEFAULT now()\n) ENGINE = MergeTree\nPARTITION BY driverID\nORDER BY (inserted_time);\n\nCREATE TABLE analytics.driver_ratings_queue(\n    rate UInt8,\n    userID Int64,\n    driverID String,\n    orderID String\n) ENGINE = Kafka\nSETTINGS kafka_broker_list = 'broker:9092',\n    kafka_topic_list = 'driver-ratings',\n    kafka_group_name = 'rating_readers',\n    kafka_format = 'Avro',\n    kafka_max_block_size = 1048576;\n\nCREATE MATERIALIZED VIEW analytics.driver_ratings_queue_mv TO analytics.driver_ratings AS\nSELECT rate, userID, driverID, orderID\nFROM analytics.driver_ratings_queue;\n\nCREATE TABLE IF NOT EXISTS analytics.user_ratings(\n    rate UInt8,\n    userID Int64,\n    driverID String,\n    orderID String,\n    inserted_time DateTime DEFAULT now()\n) ENGINE = MergeTree\n    PARTITION BY userID\n    ORDER BY (inserted_time);\n\nCREATE TABLE analytics.user_ratings_queue(\n    rate UInt8,\n    userID Int64,\n    driverID String,\n    orderID String\n) ENGINE = Kafka\nSETTINGS kafka_broker_list = 'broker:9092',\n    kafka_topic_list = 'user-ratings',\n    kafka_group_name = 'rating_readers',\n    kafka_format = 'JSON',\n    kafka_max_block_size = 1048576;\n\nCREATE MATERIALIZED VIEW analytics.user_ratings_queue_mv TO analytics.user_ratings AS\nSELECT rate, userID, driverID, orderID\nFROM analytics.user_ratings_queue;\n\nCREATE TABLE IF NOT EXISTS analytics.orders(\n    from_place String,\n    to_place String,\n    userID Int64,\n    driverID String,\n    orderID String,\n    inserted_time DateTime DEFAULT now()\n) ENGINE = MergeTree\n    PARTITION BY driverID\n    ORDER BY (inserted_time);\n\nCREATE TABLE analytics.orders_queue(\n    from_place String,\n    to_place String,\n    userID Int64,\n    driverID String,\n    orderID String\n) ENGINE = Kafka\nSETTINGS kafka_broker_list = 'broker:9092',\n    kafka_topic_list = 'orders',\n    kafka_group_name = 'order_readers',\n    kafka_format = 'Avro',\n    kafka_max_block_size = 1048576;\n\nCREATE MATERIALIZED VIEW analytics.orders_queue_mv TO orders AS\nSELECT from_place, to_place, userID, driverID, orderID\nFROM analytics.orders_queue;\n"
  },
  {
    "path": "database/cockroachdb/README.md",
    "content": "# cockroachdb\n\n`cockroachdb://user:password@host:port/dbname?query` (`cockroach://`, and `crdb-postgres://` work, too)\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |\n| `x-lock-table` | `LockTable` | Name of the table which maintains the migration lock |\n| `x-force-lock` | `ForceLock` | Force lock acquisition to fix faulty migrations which may not have released the schema lock (Boolean, default is `false`) |\n| `dbname` | `DatabaseName` | The name of the database to connect to |\n| `user` | | The user to sign in as |\n| `password` | | The user's password |\n| `host` | | The host to connect to. Values that start with / are for unix domain sockets. (default is localhost) |\n| `port` | | The port to bind to. (default is 5432) |\n| `connect_timeout` | | Maximum wait for connection, in seconds. Zero or not specified means wait indefinitely. |\n| `sslcert` | | Cert file location. The file must contain PEM encoded data. |\n| `sslkey` | | Key file location. The file must contain PEM encoded data. |\n| `sslrootcert` | | The location of the root certificate file. The file must contain PEM encoded data. |\n| `sslmode` | | Whether or not to use SSL (disable\\|require\\|verify-ca\\|verify-full) |\n"
  },
  {
    "path": "database/cockroachdb/TUTORIAL.md",
    "content": "# CockroachDB tutorial for beginners (insecure cluster)\n\n## Create/configure database\n\nFirst, let's start a local cluster - follow step 1. and 2. from [the docs](https://www.cockroachlabs.com/docs/stable/start-a-local-cluster.html#step-1-start-the-first-node).\n\nOnce you have it, create a database. Here I am going to create a database called `example`.\nOur user here is `cockroach`. We are not going to use a password, since it's not supported for insecure cluster.\n```\ncockroach sql --insecure --host=localhost:26257\n```\n```\nCREATE DATABASE example;\nCREATE USER IF NOT EXISTS cockroach;\nGRANT ALL ON DATABASE example TO cockroach;\n```\n\nWhen using Migrate CLI we need to pass to database URL. Let's export it to a variable for convenience:\n```\nexport COCKROACHDB_URL='cockroachdb://cockroach:@localhost:26257/example?sslmode=disable'\n```\n`sslmode=disable` means that the connection with our database will not be encrypted. This is needed to connect to an insecure node.\n\n**NOTE:** Do not use COCKROACH_URL as a variable name here, it's already in use for discrete parameters and you may run into connection problems. For more info check out [docs](https://www.cockroachlabs.com/docs/stable/connection-parameters.html#connect-using-discrete-parameters).\n\nYou can find further description of database URLs [here](README.md#database-urls).\n\n## Create migrations\nLet's create a table called `users`:\n```\nmigrate create -ext sql -dir db/migrations -seq create_users_table\n```\nIf there were no errors, we should have two files available under `db/migrations` folder:\n- 000001_create_users_table.down.sql\n- 000001_create_users_table.up.sql\n\nNote the `sql` extension that we provided.\n\nIn the `.up.sql` file let's create the table:\n```\nCREATE TABLE IF NOT EXISTS example.users\n(\n   user_id INT PRIMARY KEY,\n   username VARCHAR (50) UNIQUE NOT NULL,\n   password VARCHAR (50) NOT NULL,\n   email VARCHAR (300) UNIQUE NOT NULL\n);\n```\nAnd in the `.down.sql` let's delete it:\n```\nDROP TABLE IF EXISTS example.users;\n```\nBy adding `IF EXISTS/IF NOT EXISTS` we are making migrations idempotent - you can read more about idempotency in [getting started](/GETTING_STARTED.md#create-migrations)\n\n## Run migrations\n```\nmigrate -database ${COCKROACHDB_URL} -path db/migrations up\n```\nLet's check if the table was created properly by running `cockroach sql --insecure --host=localhost:26257 -e \"show columns from example.users;\"`.\nThe output you are supposed to see:\n```\n  column_name |  data_type   | is_nullable | column_default | generation_expression |                   indices                    | is_hidden\n+-------------+--------------+-------------+----------------+-----------------------+----------------------------------------------+-----------+\n  user_id     | INT8         |    false    | NULL           |                       | {primary,users_username_key,users_email_key} |   false\n  username    | VARCHAR(50)  |    false    | NULL           |                       | {users_username_key}                         |   false\n  password    | VARCHAR(50)  |    false    | NULL           |                       | {}                                           |   false\n  email       | VARCHAR(300) |    false    | NULL           |                       | {users_email_key}                            |   false\n(4 rows)\n```\nNow let's check if running reverse migration also works:\n```\nmigrate -database ${COCKROACHDB_URL} -path db/migrations down\n```\nMake sure to check if your database changed as expected in this case as well.\n\n## Database transactions\n\nTo show database transactions usage, let's create another set of migrations by running:\n```\nmigrate create -ext sql -dir db/migrations -seq add_mood_to_users\n```\nAgain, it should create for us two migrations files:\n- 000002_add_mood_to_users.down.sql\n- 000002_add_mood_to_users.up.sql\n\nIn Cockroach, when we want our queries to be done in a transaction, we need to wrap it with `BEGIN` and `COMMIT` commands, similar to PostgreSQL.\nIn our example, we are going to add a column to our database that can only accept enumerable values or NULL.\nMigration up:\n```\nBEGIN;\n\nALTER TABLE example.users ADD COLUMN mood STRING;\nALTER TABLE example.users ADD CONSTRAINT check_mood CHECK (mood IN ('happy', 'sad', 'neutral'));\n\nCOMMIT;\n```\nMigration down:\n```\nALTER TABLE example.users DROP COLUMN mood;\n```\n\nNow we can run our new migration and check the database:\n```\nmigrate -database ${COCKROACHDB_URL} -path db/migrations up\ncockroach sql --insecure --host=localhost:26257 -e \"show columns from example.users;\"\n```\nExpected output:\n```\n  column_name |  data_type   | is_nullable | column_default | generation_expression |                   indices                    | is_hidden  \n+-------------+--------------+-------------+----------------+-----------------------+----------------------------------------------+-----------+\n  user_id     | INT8         |    false    | NULL           |                       | {primary,users_username_key,users_email_key} |   false    \n  username    | VARCHAR(50)  |    false    | NULL           |                       | {users_username_key}                         |   false    \n  password    | VARCHAR(50)  |    false    | NULL           |                       | {}                                           |   false    \n  email       | VARCHAR(300) |    false    | NULL           |                       | {users_email_key}                            |   false    \n  mood        | STRING       |    true     | NULL           |                       | {}                                           |   false    \n(5 rows)\n```\n\n## Optional: Run migrations within your Go app\nHere is a very simple app running migrations for the above configuration:\n```\nimport (\n\t\"log\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t_ \"github.com/golang-migrate/migrate/v4/database/cockroachdb\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nfunc main() {\n\tm, err := migrate.New(\n\t\t\"file://db/migrations\",\n\t\t\"cockroachdb://cockroach:@localhost:26257/example?sslmode=disable\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif err := m.Up(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n```\nYou can find details [here](README.md#use-in-your-go-project)\n"
  },
  {
    "path": "database/cockroachdb/cockroachdb.go",
    "content": "package cockroachdb\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"sync/atomic\"\n\n\t\"github.com/cockroachdb/cockroach-go/v2/crdb\"\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t\"github.com/lib/pq\"\n)\n\nfunc init() {\n\tdb := CockroachDb{}\n\tdatabase.Register(\"cockroach\", &db)\n\tdatabase.Register(\"cockroachdb\", &db)\n\tdatabase.Register(\"crdb-postgres\", &db)\n}\n\nvar DefaultMigrationsTable = \"schema_migrations\"\nvar DefaultLockTable = \"schema_lock\"\n\nvar (\n\tErrNilConfig      = fmt.Errorf(\"no config\")\n\tErrNoDatabaseName = fmt.Errorf(\"no database name\")\n)\n\ntype Config struct {\n\tMigrationsTable string\n\tLockTable       string\n\tForceLock       bool\n\tDatabaseName    string\n}\n\ntype CockroachDb struct {\n\tdb       *sql.DB\n\tisLocked atomic.Bool\n\n\t// Open and WithInstance need to guarantee that config is never nil\n\tconfig *Config\n}\n\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif err := instance.Ping(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif config.DatabaseName == \"\" {\n\t\tquery := `SELECT current_database()`\n\t\tvar databaseName string\n\t\tif err := instance.QueryRow(query).Scan(&databaseName); err != nil {\n\t\t\treturn nil, &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\tif len(databaseName) == 0 {\n\t\t\treturn nil, ErrNoDatabaseName\n\t\t}\n\n\t\tconfig.DatabaseName = databaseName\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tif len(config.LockTable) == 0 {\n\t\tconfig.LockTable = DefaultLockTable\n\t}\n\n\tpx := &CockroachDb{\n\t\tdb:     instance,\n\t\tconfig: config,\n\t}\n\n\t// ensureVersionTable is a locking operation, so we need to ensureLockTable before we ensureVersionTable.\n\tif err := px.ensureLockTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := px.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\nfunc (c *CockroachDb) Open(url string) (database.Driver, error) {\n\tpurl, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// As Cockroach uses the postgres protocol, and 'postgres' is already a registered database, we need to replace the\n\t// connect prefix, with the actual protocol, so that the library can differentiate between the implementations\n\tre := regexp.MustCompile(\"^(cockroach(db)?|crdb-postgres)\")\n\tconnectString := re.ReplaceAllString(migrate.FilterCustomQuery(purl).String(), \"postgres\")\n\n\tdb, err := sql.Open(\"postgres\", connectString)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmigrationsTable := purl.Query().Get(\"x-migrations-table\")\n\tif len(migrationsTable) == 0 {\n\t\tmigrationsTable = DefaultMigrationsTable\n\t}\n\n\tlockTable := purl.Query().Get(\"x-lock-table\")\n\tif len(lockTable) == 0 {\n\t\tlockTable = DefaultLockTable\n\t}\n\n\tforceLockQuery := purl.Query().Get(\"x-force-lock\")\n\tforceLock, err := strconv.ParseBool(forceLockQuery)\n\tif err != nil {\n\t\tforceLock = false\n\t}\n\n\tpx, err := WithInstance(db, &Config{\n\t\tDatabaseName:    purl.Path,\n\t\tMigrationsTable: migrationsTable,\n\t\tLockTable:       lockTable,\n\t\tForceLock:       forceLock,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\nfunc (c *CockroachDb) Close() error {\n\treturn c.db.Close()\n}\n\n// Locking is done manually with a separate lock table.  Implementing advisory locks in CRDB is being discussed\n// See: https://github.com/cockroachdb/cockroach/issues/13546\nfunc (c *CockroachDb) Lock() error {\n\treturn database.CasRestoreOnErr(&c.isLocked, false, true, database.ErrLocked, func() (err error) {\n\t\treturn crdb.ExecuteTx(context.Background(), c.db, nil, func(tx *sql.Tx) (err error) {\n\t\t\taid, err := database.GenerateAdvisoryLockId(c.config.DatabaseName)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tquery := \"SELECT * FROM \" + c.config.LockTable + \" WHERE lock_id = $1\"\n\t\t\trows, err := tx.Query(query, aid)\n\t\t\tif err != nil {\n\t\t\t\treturn database.Error{OrigErr: err, Err: \"failed to fetch migration lock\", Query: []byte(query)}\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\tif errClose := rows.Close(); errClose != nil {\n\t\t\t\t\terr = errors.Join(err, errClose)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// If row exists at all, lock is present\n\t\t\tlocked := rows.Next()\n\t\t\tif locked && !c.config.ForceLock {\n\t\t\t\treturn database.ErrLocked\n\t\t\t}\n\n\t\t\tquery = \"INSERT INTO \" + c.config.LockTable + \" (lock_id) VALUES ($1)\"\n\t\t\tif _, err := tx.Exec(query, aid); err != nil {\n\t\t\t\treturn database.Error{OrigErr: err, Err: \"failed to set migration lock\", Query: []byte(query)}\n\t\t\t}\n\n\t\t\treturn nil\n\t\t})\n\t})\n}\n\n// Locking is done manually with a separate lock table.  Implementing advisory locks in CRDB is being discussed\n// See: https://github.com/cockroachdb/cockroach/issues/13546\nfunc (c *CockroachDb) Unlock() error {\n\treturn database.CasRestoreOnErr(&c.isLocked, true, false, database.ErrNotLocked, func() (err error) {\n\t\taid, err := database.GenerateAdvisoryLockId(c.config.DatabaseName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// In the event of an implementation (non-migration) error, it is possible for the lock to not be released.  Until\n\t\t// a better locking mechanism is added, a manual purging of the lock table may be required in such circumstances\n\t\tquery := \"DELETE FROM \" + c.config.LockTable + \" WHERE lock_id = $1\"\n\t\tif _, err := c.db.Exec(query, aid); err != nil {\n\t\t\tif e, ok := err.(*pq.Error); ok {\n\t\t\t\t// 42P01 is \"UndefinedTableError\" in CockroachDB\n\t\t\t\t// https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/pgwire/pgerror/codes.go\n\t\t\t\tif e.Code == \"42P01\" {\n\t\t\t\t\t// On drops, the lock table is fully removed;  This is fine, and is a valid \"unlocked\" state for the schema\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn database.Error{OrigErr: err, Err: \"failed to release migration lock\", Query: []byte(query)}\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc (c *CockroachDb) Run(migration io.Reader) error {\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// run migration\n\tquery := string(migr[:])\n\tif _, err := c.db.Exec(query); err != nil {\n\t\treturn database.Error{OrigErr: err, Err: \"migration failed\", Query: migr}\n\t}\n\n\treturn nil\n}\n\nfunc (c *CockroachDb) SetVersion(version int, dirty bool) error {\n\treturn crdb.ExecuteTx(context.Background(), c.db, nil, func(tx *sql.Tx) error {\n\t\tif _, err := tx.Exec(`DELETE FROM \"` + c.config.MigrationsTable + `\"`); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Also re-write the schema version for nil dirty versions to prevent\n\t\t// empty schema version for failed down migration on the first migration\n\t\t// See: https://github.com/golang-migrate/migrate/issues/330\n\t\tif version >= 0 || (version == database.NilVersion && dirty) {\n\t\t\tif _, err := tx.Exec(`INSERT INTO \"`+c.config.MigrationsTable+`\" (version, dirty) VALUES ($1, $2)`, version, dirty); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc (c *CockroachDb) Version() (version int, dirty bool, err error) {\n\tquery := `SELECT version, dirty FROM \"` + c.config.MigrationsTable + `\" LIMIT 1`\n\terr = c.db.QueryRow(query).Scan(&version, &dirty)\n\n\tswitch {\n\tcase err == sql.ErrNoRows:\n\t\treturn database.NilVersion, false, nil\n\n\tcase err != nil:\n\t\tif e, ok := err.(*pq.Error); ok {\n\t\t\t// 42P01 is \"UndefinedTableError\" in CockroachDB\n\t\t\t// https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/pgwire/pgerror/codes.go\n\t\t\tif e.Code == \"42P01\" {\n\t\t\t\treturn database.NilVersion, false, nil\n\t\t\t}\n\t\t}\n\t\treturn 0, false, &database.Error{OrigErr: err, Query: []byte(query)}\n\n\tdefault:\n\t\treturn version, dirty, nil\n\t}\n}\n\nfunc (c *CockroachDb) Drop() (err error) {\n\t// select all tables in current schema\n\tquery := `SELECT table_name FROM information_schema.tables WHERE table_schema=(SELECT current_schema())`\n\ttables, err := c.db.Query(query)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tdefer func() {\n\t\tif errClose := tables.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\t// delete one table after another\n\ttableNames := make([]string, 0)\n\tfor tables.Next() {\n\t\tvar tableName string\n\t\tif err := tables.Scan(&tableName); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(tableName) > 0 {\n\t\t\ttableNames = append(tableNames, tableName)\n\t\t}\n\t}\n\tif err := tables.Err(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tif len(tableNames) > 0 {\n\t\t// delete one by one ...\n\t\tfor _, t := range tableNames {\n\t\t\tquery = `DROP TABLE IF EXISTS ` + t + ` CASCADE`\n\t\t\tif _, err := c.db.Exec(query); err != nil {\n\t\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database, which deviates from the usual\n// convention of \"caller locks\" in the CockroachDb type.\nfunc (c *CockroachDb) ensureVersionTable() (err error) {\n\tif err = c.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := c.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\t// check if migration table exists\n\tvar count int\n\tquery := `SELECT COUNT(1) FROM information_schema.tables WHERE table_name = $1 AND table_schema = (SELECT current_schema()) LIMIT 1`\n\tif err := c.db.QueryRow(query, c.config.MigrationsTable).Scan(&count); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tif count == 1 {\n\t\treturn nil\n\t}\n\n\t// if not, create the empty migration table\n\tquery = `CREATE TABLE \"` + c.config.MigrationsTable + `\" (version INT NOT NULL PRIMARY KEY, dirty BOOL NOT NULL)`\n\tif _, err := c.db.Exec(query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\treturn nil\n}\n\nfunc (c *CockroachDb) ensureLockTable() error {\n\t// check if lock table exists\n\tvar count int\n\tquery := `SELECT COUNT(1) FROM information_schema.tables WHERE table_name = $1 AND table_schema = (SELECT current_schema()) LIMIT 1`\n\tif err := c.db.QueryRow(query, c.config.LockTable).Scan(&count); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tif count == 1 {\n\t\treturn nil\n\t}\n\n\t// if not, create the empty lock table\n\tquery = `CREATE TABLE \"` + c.config.LockTable + `\" (lock_id INT NOT NULL PRIMARY KEY)`\n\tif _, err := c.db.Exec(query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "database/cockroachdb/cockroachdb_test.go",
    "content": "package cockroachdb\n\n// error codes https://github.com/lib/pq/blob/master/error.go\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"log\"\n\t\"strings\"\n\t\"testing\"\n)\n\nimport (\n\t\"github.com/dhui/dktest\"\n\t_ \"github.com/lib/pq\"\n)\n\nimport (\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t\"github.com/golang-migrate/migrate/v4/dktesting\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nconst defaultPort = 26257\n\nvar (\n\topts = dktest.Options{Cmd: []string{\"start-single-node\", \"--insecure\"}, PortRequired: true, ReadyFunc: isReady}\n\t// Supported versions: https://www.cockroachlabs.com/docs/releases/release-support-policy#supported-versions\n\tspecs = []dktesting.ContainerSpec{\n\t\t{ImageName: \"cockroachdb/cockroach:latest-v24.3\", Options: opts},\n\t\t{ImageName: \"cockroachdb/cockroach:latest-v24.1\", Options: opts},\n\t\t{ImageName: \"cockroachdb/cockroach:latest-v23.2\", Options: opts},\n\t\t{ImageName: \"cockroachdb/cockroach:latest-v23.1\", Options: opts},\n\t}\n)\n\nfunc isReady(ctx context.Context, c dktest.ContainerInfo) bool {\n\tip, port, err := c.Port(defaultPort)\n\tif err != nil {\n\t\tlog.Println(\"port error:\", err)\n\t\treturn false\n\t}\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"postgres://root@%v:%v?sslmode=disable\", ip, port))\n\tif err != nil {\n\t\tlog.Println(\"open error:\", err)\n\t\treturn false\n\t}\n\tif err := db.PingContext(ctx); err != nil {\n\t\tlog.Println(\"ping error:\", err)\n\t\treturn false\n\t}\n\tif err := db.Close(); err != nil {\n\t\tlog.Println(\"close error:\", err)\n\t}\n\treturn true\n}\n\nfunc createDB(t *testing.T, c dktest.ContainerInfo) {\n\tip, port, err := c.Port(defaultPort)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"postgres://root@%v:%v?sslmode=disable\", ip, port))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err = db.Ping(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\tif _, err = db.Exec(\"CREATE DATABASE migrate\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc Test(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, ci dktest.ContainerInfo) {\n\t\tcreateDB(t, ci)\n\n\t\tip, port, err := ci.Port(26257)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fmt.Sprintf(\"cockroach://root@%v:%v/migrate?sslmode=disable\", ip, port)\n\t\tc := &CockroachDb{}\n\t\td, err := c.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\t})\n}\n\nfunc TestMigrate(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, ci dktest.ContainerInfo) {\n\t\tcreateDB(t, ci)\n\n\t\tip, port, err := ci.Port(26257)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fmt.Sprintf(\"cockroach://root@%v:%v/migrate?sslmode=disable\", ip, port)\n\t\tc := &CockroachDb{}\n\t\td, err := c.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://./examples/migrations\", \"migrate\", d)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\t})\n}\n\nfunc TestMultiStatement(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, ci dktest.ContainerInfo) {\n\t\tcreateDB(t, ci)\n\n\t\tip, port, err := ci.Port(26257)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fmt.Sprintf(\"cockroach://root@%v:%v/migrate?sslmode=disable\", ip, port)\n\t\tc := &CockroachDb{}\n\t\td, err := c.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := d.Run(strings.NewReader(\"CREATE TABLE foo (foo text); CREATE TABLE bar (bar text);\")); err != nil {\n\t\t\tt.Fatalf(\"expected err to be nil, got %v\", err)\n\t\t}\n\n\t\t// make sure second table exists\n\t\tvar exists bool\n\t\tif err := d.(*CockroachDb).db.QueryRow(\"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'bar' AND table_schema = (SELECT current_schema()))\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Fatalf(\"expected table bar to exist\")\n\t\t}\n\t})\n}\n\nfunc TestFilterCustomQuery(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, ci dktest.ContainerInfo) {\n\t\tcreateDB(t, ci)\n\n\t\tip, port, err := ci.Port(26257)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fmt.Sprintf(\"cockroach://root@%v:%v/migrate?sslmode=disable&x-custom=foobar\", ip, port)\n\t\tc := &CockroachDb{}\n\t\t_, err = c.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "database/cockroachdb/examples/migrations/1085649617_create_users_table.down.sql",
    "content": "DROP TABLE IF EXISTS users;\n"
  },
  {
    "path": "database/cockroachdb/examples/migrations/1085649617_create_users_table.up.sql",
    "content": "CREATE TABLE users (\n  user_id INT UNIQUE,\n  name    STRING(40),\n  email   STRING(40)\n);\n"
  },
  {
    "path": "database/cockroachdb/examples/migrations/1185749658_add_city_to_users.down.sql",
    "content": "ALTER TABLE users DROP COLUMN IF EXISTS city;\n"
  },
  {
    "path": "database/cockroachdb/examples/migrations/1185749658_add_city_to_users.up.sql",
    "content": "ALTER TABLE users ADD COLUMN city TEXT;\n"
  },
  {
    "path": "database/cockroachdb/examples/migrations/1285849751_add_index_on_user_emails.down.sql",
    "content": "DROP INDEX IF EXISTS users_email_index;\n"
  },
  {
    "path": "database/cockroachdb/examples/migrations/1285849751_add_index_on_user_emails.up.sql",
    "content": "CREATE UNIQUE INDEX IF NOT EXISTS users_email_index ON users (email);\n\n-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/cockroachdb/examples/migrations/1385949617_create_books_table.down.sql",
    "content": "DROP TABLE IF EXISTS books;\n"
  },
  {
    "path": "database/cockroachdb/examples/migrations/1385949617_create_books_table.up.sql",
    "content": "CREATE TABLE books (\n  user_id INT,\n  name    STRING(40),\n  author  STRING(40)\n);\n"
  },
  {
    "path": "database/cockroachdb/examples/migrations/1485949617_create_movies_table.down.sql",
    "content": "DROP TABLE IF EXISTS movies;\n"
  },
  {
    "path": "database/cockroachdb/examples/migrations/1485949617_create_movies_table.up.sql",
    "content": "CREATE TABLE movies (\n  user_id   INT,\n  name      STRING(40),\n  director  STRING(40)\n);\n"
  },
  {
    "path": "database/cockroachdb/examples/migrations/1585849751_just_a_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/cockroachdb/examples/migrations/1685849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/cockroachdb/examples/migrations/1785849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/cockroachdb/examples/migrations/1885849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/crate/README.md",
    "content": ""
  },
  {
    "path": "database/driver.go",
    "content": "// Package database provides the Driver interface.\n// All database drivers must implement this interface, register themselves,\n// optionally provide a `WithInstance` function and pass the tests\n// in package database/testing.\npackage database\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\n\tiurl \"github.com/golang-migrate/migrate/v4/internal/url\"\n)\n\nvar (\n\tErrLocked    = fmt.Errorf(\"can't acquire lock\")\n\tErrNotLocked = fmt.Errorf(\"can't unlock, as not currently locked\")\n)\n\nconst NilVersion int = -1\n\nvar driversMu sync.RWMutex\nvar drivers = make(map[string]Driver)\n\n// Driver is the interface every database driver must implement.\n//\n// How to implement a database driver?\n//  1. Implement this interface.\n//  2. Optionally, add a function named `WithInstance`.\n//     This function should accept an existing DB instance and a Config{} struct\n//     and return a driver instance.\n//  3. Add a test that calls database/testing.go:Test()\n//  4. Add own tests for Open(), WithInstance() (when provided) and Close().\n//     All other functions are tested by tests in database/testing.\n//     Saves you some time and makes sure all database drivers behave the same way.\n//  5. Call Register in init().\n//  6. Create a internal/cli/build_<driver-name>.go file\n//  7. Add driver name in 'DATABASE' variable in Makefile\n//\n// Guidelines:\n//   - Don't try to correct user input. Don't assume things.\n//     When in doubt, return an error and explain the situation to the user.\n//   - All configuration input must come from the URL string in func Open()\n//     or the Config{} struct in WithInstance. Don't os.Getenv().\ntype Driver interface {\n\t// Open returns a new driver instance configured with parameters\n\t// coming from the URL string. Migrate will call this function\n\t// only once per instance.\n\tOpen(url string) (Driver, error)\n\n\t// Close closes the underlying database instance managed by the driver.\n\t// Migrate will call this function only once per instance.\n\tClose() error\n\n\t// Lock should acquire a database lock so that only one migration process\n\t// can run at a time. Migrate will call this function before Run is called.\n\t// If the implementation can't provide this functionality, return nil.\n\t// Return database.ErrLocked if database is already locked.\n\tLock() error\n\n\t// Unlock should release the lock. Migrate will call this function after\n\t// all migrations have been run.\n\tUnlock() error\n\n\t// Run applies a migration to the database. migration is guaranteed to be not nil.\n\tRun(migration io.Reader) error\n\n\t// SetVersion saves version and dirty state.\n\t// Migrate will call this function before and after each call to Run.\n\t// version must be >= -1. -1 means NilVersion.\n\tSetVersion(version int, dirty bool) error\n\n\t// Version returns the currently active version and if the database is dirty.\n\t// When no migration has been applied, it must return version -1.\n\t// Dirty means, a previous migration failed and user interaction is required.\n\tVersion() (version int, dirty bool, err error)\n\n\t// Drop deletes everything in the database.\n\t// Note that this is a breaking action, a new call to Open() is necessary to\n\t// ensure subsequent calls work as expected.\n\tDrop() error\n}\n\n// Open returns a new driver instance.\nfunc Open(url string) (Driver, error) {\n\tscheme, err := iurl.SchemeFromURL(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdriversMu.RLock()\n\td, ok := drivers[scheme]\n\tdriversMu.RUnlock()\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"database driver: unknown driver %v (forgotten import?)\", scheme)\n\t}\n\n\treturn d.Open(url)\n}\n\n// Register globally registers a driver.\nfunc Register(name string, driver Driver) {\n\tdriversMu.Lock()\n\tdefer driversMu.Unlock()\n\tif driver == nil {\n\t\tpanic(\"Register driver is nil\")\n\t}\n\tif _, dup := drivers[name]; dup {\n\t\tpanic(\"Register called twice for driver \" + name)\n\t}\n\tdrivers[name] = driver\n}\n\n// List lists the registered drivers\nfunc List() []string {\n\tdriversMu.RLock()\n\tdefer driversMu.RUnlock()\n\tnames := make([]string, 0, len(drivers))\n\tfor n := range drivers {\n\t\tnames = append(names, n)\n\t}\n\treturn names\n}\n"
  },
  {
    "path": "database/driver_test.go",
    "content": "package database\n\nimport (\n\t\"io\"\n\t\"testing\"\n)\n\nfunc ExampleDriver() {\n\t// see database/stub for an example\n\n\t// database/stub/stub.go has the driver implementation\n\t// database/stub/stub_test.go runs database/testing/test.go:Test\n}\n\n// Using database/stub here is not possible as it\n// results in an import cycle.\ntype mockDriver struct {\n\turl string\n}\n\nfunc (m *mockDriver) Open(url string) (Driver, error) {\n\treturn &mockDriver{\n\t\turl: url,\n\t}, nil\n}\n\nfunc (m *mockDriver) Close() error {\n\treturn nil\n}\n\nfunc (m *mockDriver) Lock() error {\n\treturn nil\n}\n\nfunc (m *mockDriver) Unlock() error {\n\treturn nil\n}\n\nfunc (m *mockDriver) Run(migration io.Reader) error {\n\treturn nil\n}\n\nfunc (m *mockDriver) SetVersion(version int, dirty bool) error {\n\treturn nil\n}\n\nfunc (m *mockDriver) Version() (version int, dirty bool, err error) {\n\treturn 0, false, nil\n}\n\nfunc (m *mockDriver) Drop() error {\n\treturn nil\n}\n\nfunc TestRegisterTwice(t *testing.T) {\n\tRegister(\"mock\", &mockDriver{})\n\n\tvar err interface{}\n\tfunc() {\n\t\tdefer func() {\n\t\t\terr = recover()\n\t\t}()\n\t\tRegister(\"mock\", &mockDriver{})\n\t}()\n\n\tif err == nil {\n\t\tt.Fatal(\"expected a panic when calling Register twice\")\n\t}\n}\n\nfunc TestOpen(t *testing.T) {\n\t// Make sure the driver is registered.\n\t// But if the previous test already registered it just ignore the panic.\n\t// If we don't do this it will be impossible to run this test standalone.\n\tfunc() {\n\t\tdefer func() {\n\t\t\t_ = recover()\n\t\t}()\n\t\tRegister(\"mock\", &mockDriver{})\n\t}()\n\n\tcases := []struct {\n\t\turl string\n\t\terr bool\n\t}{\n\t\t{\n\t\t\t\"mock://user:pass@tcp(host:1337)/db\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"unknown://bla\",\n\t\t\ttrue,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.url, func(t *testing.T) {\n\t\t\td, err := Open(c.url)\n\n\t\t\tif err == nil {\n\t\t\t\tif c.err {\n\t\t\t\t\tt.Fatal(\"expected an error for an unknown driver\")\n\t\t\t\t} else {\n\t\t\t\t\tif md, ok := d.(*mockDriver); !ok {\n\t\t\t\t\t\tt.Fatalf(\"expected *mockDriver got %T\", d)\n\t\t\t\t\t} else if md.url != c.url {\n\t\t\t\t\t\tt.Fatalf(\"expected %q got %q\", c.url, md.url)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if !c.err {\n\t\t\t\tt.Fatalf(\"did not expect %q\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "database/error.go",
    "content": "package database\n\nimport (\n\t\"fmt\"\n)\n\n// Error should be used for errors involving queries ran against the database\ntype Error struct {\n\t// Optional: the line number\n\tLine uint\n\n\t// Query is a query excerpt\n\tQuery []byte\n\n\t// Err is a useful/helping error message for humans\n\tErr string\n\n\t// OrigErr is the underlying error\n\tOrigErr error\n}\n\nfunc (e Error) Error() string {\n\tif len(e.Err) == 0 {\n\t\treturn fmt.Sprintf(\"%v in line %v: %s\", e.OrigErr, e.Line, e.Query)\n\t}\n\treturn fmt.Sprintf(\"%v in line %v: %s (details: %v)\", e.Err, e.Line, e.Query, e.OrigErr)\n}\n"
  },
  {
    "path": "database/firebird/README.md",
    "content": "# firebird\n\n`firebirdsql://user:password@servername[:port_number]/database_name_or_file[?params1=value1[&param2=value2]...]`\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |\n| `auth_plugin_name` | | Authentication plugin name. Srp256/Srp/Legacy_Auth are available. (default is Srp) |\n| `column_name_to_lower` | | Force column name to lower. (default is false) |\n| `role` | | Role name |\n| `tzname` | | Time Zone name. (For Firebird 4.0+) |\n| `wire_crypt` | | Enable wire data encryption or not. For Firebird 3.0+ (default is true) |\n"
  },
  {
    "path": "database/firebird/examples/migrations/1085649617_create_users_table.down.sql",
    "content": "DROP TABLE users;\n"
  },
  {
    "path": "database/firebird/examples/migrations/1085649617_create_users_table.up.sql",
    "content": "CREATE TABLE users (\n  user_id integer unique,\n  name    varchar(40),\n  email   varchar(40)\n);\n"
  },
  {
    "path": "database/firebird/examples/migrations/1185749658_add_city_to_users.down.sql",
    "content": "ALTER TABLE users DROP city;\n"
  },
  {
    "path": "database/firebird/examples/migrations/1185749658_add_city_to_users.up.sql",
    "content": "ALTER TABLE users ADD city varchar(100);\n\n\n"
  },
  {
    "path": "database/firebird/examples/migrations/1285849751_add_index_on_user_emails.down.sql",
    "content": "DROP INDEX users_email_index;\n"
  },
  {
    "path": "database/firebird/examples/migrations/1285849751_add_index_on_user_emails.up.sql",
    "content": "CREATE UNIQUE INDEX users_email_index ON users (email);\n\n-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/firebird/examples/migrations/1385949617_create_books_table.down.sql",
    "content": "DROP TABLE books;\n"
  },
  {
    "path": "database/firebird/examples/migrations/1385949617_create_books_table.up.sql",
    "content": "CREATE TABLE books (\n  user_id integer,\n  name    varchar(40),\n  author  varchar(40)\n);\n"
  },
  {
    "path": "database/firebird/examples/migrations/1485949617_create_movies_table.down.sql",
    "content": "DROP TABLE movies;\n"
  },
  {
    "path": "database/firebird/examples/migrations/1485949617_create_movies_table.up.sql",
    "content": "CREATE TABLE movies (\n  user_id   integer,\n  name      varchar(40),\n  director  varchar(40)\n);\n"
  },
  {
    "path": "database/firebird/firebird.go",
    "content": "//go:build go1.9\n\npackage firebird\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"sync/atomic\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t_ \"github.com/nakagami/firebirdsql\"\n)\n\nfunc init() {\n\tdb := Firebird{}\n\tdatabase.Register(\"firebird\", &db)\n\tdatabase.Register(\"firebirdsql\", &db)\n}\n\nvar DefaultMigrationsTable = \"schema_migrations\"\n\nvar (\n\tErrNilConfig = fmt.Errorf(\"no config\")\n)\n\ntype Config struct {\n\tDatabaseName    string\n\tMigrationsTable string\n}\n\ntype Firebird struct {\n\t// Locking and unlocking need to use the same connection\n\tconn     *sql.Conn\n\tdb       *sql.DB\n\tisLocked atomic.Bool\n\n\t// Open and WithInstance need to guarantee that config is never nil\n\tconfig *Config\n}\n\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif err := instance.Ping(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tconn, err := instance.Conn(context.Background())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfb := &Firebird{\n\t\tconn:   conn,\n\t\tdb:     instance,\n\t\tconfig: config,\n\t}\n\n\tif err := fb.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn fb, nil\n}\n\nfunc (f *Firebird) Open(dsn string) (database.Driver, error) {\n\tpurl, err := nurl.Parse(dsn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdb, err := sql.Open(\"firebirdsql\", migrate.FilterCustomQuery(purl).String())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpx, err := WithInstance(db, &Config{\n\t\tMigrationsTable: purl.Query().Get(\"x-migrations-table\"),\n\t\tDatabaseName:    purl.Path,\n\t})\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\nfunc (f *Firebird) Close() error {\n\tconnErr := f.conn.Close()\n\tdbErr := f.db.Close()\n\tif connErr != nil || dbErr != nil {\n\t\treturn fmt.Errorf(\"conn: %v, db: %v\", connErr, dbErr)\n\t}\n\treturn nil\n}\n\nfunc (f *Firebird) Lock() error {\n\tif !f.isLocked.CompareAndSwap(false, true) {\n\t\treturn database.ErrLocked\n\t}\n\treturn nil\n}\n\nfunc (f *Firebird) Unlock() error {\n\tif !f.isLocked.CompareAndSwap(true, false) {\n\t\treturn database.ErrNotLocked\n\t}\n\treturn nil\n}\n\nfunc (f *Firebird) Run(migration io.Reader) error {\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// run migration\n\tquery := string(migr[:])\n\tif _, err := f.conn.ExecContext(context.Background(), query); err != nil {\n\t\treturn database.Error{OrigErr: err, Err: \"migration failed\", Query: migr}\n\t}\n\n\treturn nil\n}\n\nfunc (f *Firebird) SetVersion(version int, dirty bool) error {\n\t// Always re-write the schema version to prevent empty schema version\n\t// for failed down migration on the first migration\n\t// See: https://github.com/golang-migrate/migrate/issues/330\n\n\t// TODO: parameterize this SQL statement\n\t//       https://firebirdsql.org/refdocs/langrefupd20-execblock.html\n\t//       VALUES (?, ?) doesn't work\n\tquery := fmt.Sprintf(`EXECUTE BLOCK AS BEGIN\n\t\t\t\t\tDELETE FROM \"%v\";\n\t\t\t\t\tINSERT INTO \"%v\" (version, dirty) VALUES (%v, %v);\n\t\t\t\tEND;`,\n\t\tf.config.MigrationsTable, f.config.MigrationsTable, version, btoi(dirty))\n\n\tif _, err := f.conn.ExecContext(context.Background(), query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn nil\n}\n\nfunc (f *Firebird) Version() (version int, dirty bool, err error) {\n\tvar d int\n\tquery := fmt.Sprintf(`SELECT FIRST 1 version, dirty FROM \"%v\"`, f.config.MigrationsTable)\n\terr = f.conn.QueryRowContext(context.Background(), query).Scan(&version, &d)\n\tswitch {\n\tcase err == sql.ErrNoRows:\n\t\treturn database.NilVersion, false, nil\n\tcase err != nil:\n\t\treturn 0, false, &database.Error{OrigErr: err, Query: []byte(query)}\n\n\tdefault:\n\t\treturn version, itob(d), nil\n\t}\n}\n\nfunc (f *Firebird) Drop() (err error) {\n\t// select all tables\n\tquery := `SELECT rdb$relation_name FROM rdb$relations WHERE rdb$view_blr IS NULL AND (rdb$system_flag IS NULL OR rdb$system_flag = 0);`\n\ttables, err := f.conn.QueryContext(context.Background(), query)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tdefer func() {\n\t\tif errClose := tables.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\t// delete one table after another\n\ttableNames := make([]string, 0)\n\tfor tables.Next() {\n\t\tvar tableName string\n\t\tif err := tables.Scan(&tableName); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(tableName) > 0 {\n\t\t\ttableNames = append(tableNames, tableName)\n\t\t}\n\t}\n\tif err := tables.Err(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\t// delete one by one ...\n\tfor _, t := range tableNames {\n\t\tquery := fmt.Sprintf(`EXECUTE BLOCK AS BEGIN\n\t\t\t\t\t\tif (not exists(select 1 from rdb$relations where rdb$relation_name = '%v')) then\n\t\t\t\t\t\texecute statement 'drop table \"%v\"';\n\t\t\t\t\tEND;`,\n\t\t\tt, t)\n\n\t\tif _, err := f.conn.ExecContext(context.Background(), query); err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\nfunc (f *Firebird) ensureVersionTable() (err error) {\n\tif err = f.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := f.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\tquery := fmt.Sprintf(`EXECUTE BLOCK AS BEGIN\n\t\t\tif (not exists(select 1 from rdb$relations where rdb$relation_name = '%v')) then\n\t\t\texecute statement 'create table \"%v\" (version bigint not null primary key, dirty smallint not null)';\n\t\tEND;`,\n\t\tf.config.MigrationsTable, f.config.MigrationsTable)\n\n\tif _, err = f.conn.ExecContext(context.Background(), query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn nil\n}\n\n// btoi converts bool to int\nfunc btoi(v bool) int {\n\tif v {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\n// itob converts int to bool\nfunc itob(v int) bool {\n\treturn v != 0\n}\n"
  },
  {
    "path": "database/firebird/firebird_test.go",
    "content": "package firebird\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\tsqldriver \"database/sql/driver\"\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/dhui/dktest\"\n\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t\"github.com/golang-migrate/migrate/v4/dktesting\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n\n\t_ \"github.com/nakagami/firebirdsql\"\n)\n\nconst (\n\tuser     = \"test_user\"\n\tpassword = \"123456\"\n\tdbName   = \"test.fdb\"\n)\n\nvar (\n\topts = dktest.Options{\n\t\tPortRequired: true,\n\t\tReadyFunc:    isReady,\n\t\tEnv: map[string]string{\n\t\t\t\"FIREBIRD_DATABASE\": dbName,\n\t\t\t\"FIREBIRD_USER\":     user,\n\t\t\t\"FIREBIRD_PASSWORD\": password,\n\t\t},\n\t}\n\tspecs = []dktesting.ContainerSpec{\n\t\t{ImageName: \"jacobalberty/firebird:v3.0\", Options: opts},\n\t\t{ImageName: \"jacobalberty/firebird:v4.0\", Options: opts},\n\t\t{ImageName: \"jacobalberty/firebird:v5.0\", Options: opts},\n\t}\n)\n\nfunc fbConnectionString(host, port string) string {\n\t//firebird://user:password@servername[:port_number]/database_name_or_file[?params1=value1[&param2=value2]...]\n\treturn fmt.Sprintf(\"firebird://%s:%s@%s:%s//firebird/data/%s\", user, password, host, port, dbName)\n}\n\nfunc isReady(ctx context.Context, c dktest.ContainerInfo) bool {\n\tip, port, err := c.FirstPort()\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tdb, err := sql.Open(\"firebirdsql\", fbConnectionString(ip, port))\n\tif err != nil {\n\t\tlog.Println(\"open error:\", err)\n\t\treturn false\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\tlog.Println(\"close error:\", err)\n\t\t}\n\t}()\n\tif err = db.PingContext(ctx); err != nil {\n\t\tswitch err {\n\t\tcase sqldriver.ErrBadConn, io.EOF:\n\t\t\treturn false\n\t\tdefault:\n\t\t\tlog.Println(err)\n\t\t}\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc Test(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fbConnectionString(ip, port)\n\t\tp := &Firebird{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tdt.Test(t, d, []byte(\"SELECT Count(*) FROM rdb$relations\"))\n\t})\n}\n\nfunc TestMigrate(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fbConnectionString(ip, port)\n\t\tp := &Firebird{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://./examples/migrations\", \"firebirdsql\", d)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\t})\n}\n\nfunc TestErrorParsing(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fbConnectionString(ip, port)\n\t\tp := &Firebird{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\twantErr := `migration failed in line 0: CREATE TABLEE foo (foo varchar(40)); (details: Dynamic SQL Error\nSQL error code = -104\nToken unknown - line 1, column 8\nTABLEE\n)`\n\n\t\tif err := d.Run(strings.NewReader(\"CREATE TABLEE foo (foo varchar(40));\")); err == nil {\n\t\t\tt.Fatal(\"expected err but got nil\")\n\t\t} else if err.Error() != wantErr {\n\t\t\tmsg := err.Error()\n\t\t\tt.Fatalf(\"expected '%s' but got '%s'\", wantErr, msg)\n\t\t}\n\t})\n}\n\nfunc TestFilterCustomQuery(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fbConnectionString(ip, port) + \"?sslmode=disable&x-custom=foobar\"\n\t\tp := &Firebird{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t})\n}\n\nfunc Test_Lock(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fbConnectionString(ip, port)\n\t\tp := &Firebird{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tdt.Test(t, d, []byte(\"SELECT Count(*) FROM rdb$relations\"))\n\n\t\tps := d.(*Firebird)\n\n\t\terr = ps.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = ps.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = ps.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = ps.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "database/mongodb/README.md",
    "content": "# MongoDB\n\n* Driver work with mongo through [db.runCommands](https://docs.mongodb.com/manual/reference/command/)\n* Migrations support json format. It contains array of commands for `db.runCommand`. Every command is executed in separate request to database \n* All keys have to be in quotes `\"`\n* [Examples](./examples)\n\n# Usage\n\n`mongodb://user:password@host:port/dbname?query` (`mongodb+srv://` also works, but behaves a bit differently. See [docs](https://docs.mongodb.com/manual/reference/connection-string/#dns-seedlist-connection-format) for more information)\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| `x-migrations-collection` | `MigrationsCollection` | Name of the migrations collection |\n| `x-transaction-mode` | `TransactionMode` | If set to `true` wrap commands in [transaction](https://docs.mongodb.com/manual/core/transactions). Available only for replica set. Driver is using [strconv.ParseBool](https://golang.org/pkg/strconv/#ParseBool) for parsing|\n| `x-advisory-locking` | `true` | Feature flag for advisory locking, if set to false, disable advisory locking |\n| `x-advisory-lock-collection` | `migrate_advisory_lock` | The name of the collection to use for advisory locking.|\n| `x-advisory-lock-timeout` | `15` | The max time in seconds that migrate will wait to acquire a lock before failing. |\n| `x-advisory-lock-timeout-interval` | `10` | The max time in seconds between attempts to acquire the advisory lock, the lock is attempted to be acquired using an exponential backoff algorithm. |\n| `dbname` | `DatabaseName` | The name of the database to connect to |\n| `user` | | The user to sign in as. Can be omitted |\n| `password` | | The user's password. Can be omitted | \n| `host` | | The host to connect to |\n| `port` | | The port to bind to |"
  },
  {
    "path": "database/mongodb/examples/migrations/001_create_user.down.json",
    "content": "[\n  {\n    \"dropUser\": \"deminem\"\n  }\n]"
  },
  {
    "path": "database/mongodb/examples/migrations/001_create_user.up.json",
    "content": "[\n  {\n    \"createUser\": \"deminem\",\n    \"pwd\": \"gogo\",\n    \"roles\": [\n      {\n        \"role\": \"readWrite\",\n        \"db\": \"testMigration\"\n      }\n    ]\n  }\n]"
  },
  {
    "path": "database/mongodb/examples/migrations/002_create_indexes.down.json",
    "content": "[\n  {\n    \"dropIndexes\": \"mycollection\",\n    \"index\": \"username_sort_by_asc_created\"\n  },\n  {\n    \"dropIndexes\": \"mycollection\",\n    \"index\": \"unique_email\"\n  }\n]"
  },
  {
    "path": "database/mongodb/examples/migrations/002_create_indexes.up.json",
    "content": "[{\n  \"createIndexes\": \"mycollection\",\n  \"indexes\": [\n    {\n      \"key\": {\n        \"username\": 1,\n        \"created\": -1\n      },\n      \"name\": \"username_sort_by_asc_created\",\n      \"background\": true\n    },\n    {\n      \"key\": {\n        \"email\": 1\n      },\n      \"name\": \"unique_email\",\n      \"unique\": true,\n      \"background\": true\n    }\n  ]\n}]"
  },
  {
    "path": "database/mongodb/examples/migrations/003_add_new_field.down.json",
    "content": "[\n    {\n        \"update\": \"users\",\n        \"updates\": [\n            {\n                \"q\": {},\n                \"u\": {\n                    \"$unset\": {\n                        \"status\": \"\"\n                    }\n                },\n                \"multi\": true\n            }\n        ]\n    }\n]\n"
  },
  {
    "path": "database/mongodb/examples/migrations/003_add_new_field.up.json",
    "content": "[\n    {\n        \"update\": \"users\",\n        \"updates\": [\n            {\n                \"q\": {},\n                \"u\": {\n                    \"$set\": {\n                        \"status\": \"active\"\n                    }\n                },\n                \"multi\": true\n            }\n        ]\n    }\n]\n"
  },
  {
    "path": "database/mongodb/examples/migrations/004_replace_field_value_from_another_field.down.json",
    "content": "[\n    {\n        \"update\": \"users\",\n        \"updates\": [\n            {\n                \"q\": {},\n                \"u\": {\n                    \"fullname\": \"\"\n                },\n                \"multi\": true\n            }\n        ]\n    }\n]\n"
  },
  {
    "path": "database/mongodb/examples/migrations/004_replace_field_value_from_another_field.up.json",
    "content": "[\n    {\n        \"aggregate\": \"users\",\n        \"pipeline\": [\n            {\n                \"$project\": {\n                    \"_id\": 1,\n                    \"firstname\": 1,\n                    \"lastname\": 1,\n                    \"username\": 1,\n                    \"password\": 1,\n                    \"email\": 1,\n                    \"active\": 1,\n                    \"fullname\": { \"$concat\": [\"$firstname\", \" \", \"$lastname\"] }\n                }\n            },\n            {\n                \"$out\": \"users\"\n            }\n        ],\n        \"cursor\": {}\n    }\n]\n"
  },
  {
    "path": "database/mongodb/mongodb.go",
    "content": "package mongodb\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"os\"\n\t\"strconv\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t\"go.mongodb.org/mongo-driver/bson\"\n\t\"go.mongodb.org/mongo-driver/mongo\"\n\t\"go.mongodb.org/mongo-driver/mongo/options\"\n\t\"go.mongodb.org/mongo-driver/x/mongo/driver/connstring\"\n)\n\nfunc init() {\n\tdb := Mongo{}\n\tdatabase.Register(\"mongodb\", &db)\n\tdatabase.Register(\"mongodb+srv\", &db)\n}\n\nvar DefaultMigrationsCollection = \"schema_migrations\"\n\nconst DefaultLockingCollection = \"migrate_advisory_lock\" // the collection to use for advisory locking by default.\nconst lockKeyUniqueValue = 0                             // the unique value to lock on. If multiple clients try to insert the same key, it will fail (locked).\nconst DefaultLockTimeout = 15                            // the default maximum time to wait for a lock to be released.\nconst DefaultLockTimeoutInterval = 10                    // the default maximum intervals time for the locking timout.\nconst DefaultAdvisoryLockingFlag = true                  // the default value for the advisory locking feature flag. Default is true.\nconst LockIndexName = \"lock_unique_key\"                  // the name of the index which adds unique constraint to the locking_key field.\nconst contextWaitTimeout = 5 * time.Second               // how long to wait for the request to mongo to block/wait for.\n\nvar (\n\tErrNoDatabaseName            = fmt.Errorf(\"no database name\")\n\tErrNilConfig                 = fmt.Errorf(\"no config\")\n\tErrLockTimeoutConfigConflict = fmt.Errorf(\"both x-advisory-lock-timeout-interval and x-advisory-lock-timout-interval were specified\")\n)\n\ntype Mongo struct {\n\tclient   *mongo.Client\n\tdb       *mongo.Database\n\tconfig   *Config\n\tisLocked atomic.Bool\n}\n\ntype Locking struct {\n\tCollectionName string\n\tTimeout        int\n\tEnabled        bool\n\tInterval       int\n}\ntype Config struct {\n\tDatabaseName         string\n\tMigrationsCollection string\n\tTransactionMode      bool\n\tLocking              Locking\n}\ntype versionInfo struct {\n\tVersion int  `bson:\"version\"`\n\tDirty   bool `bson:\"dirty\"`\n}\n\ntype lockObj struct {\n\tKey       int       `bson:\"locking_key\"`\n\tPid       int       `bson:\"pid\"`\n\tHostname  string    `bson:\"hostname\"`\n\tCreatedAt time.Time `bson:\"created_at\"`\n}\ntype findFilter struct {\n\tKey int `bson:\"locking_key\"`\n}\n\nfunc WithInstance(instance *mongo.Client, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\tif len(config.DatabaseName) == 0 {\n\t\treturn nil, ErrNoDatabaseName\n\t}\n\tif len(config.MigrationsCollection) == 0 {\n\t\tconfig.MigrationsCollection = DefaultMigrationsCollection\n\t}\n\tif len(config.Locking.CollectionName) == 0 {\n\t\tconfig.Locking.CollectionName = DefaultLockingCollection\n\t}\n\tif config.Locking.Timeout <= 0 {\n\t\tconfig.Locking.Timeout = DefaultLockTimeout\n\t}\n\tif config.Locking.Interval <= 0 {\n\t\tconfig.Locking.Interval = DefaultLockTimeoutInterval\n\t}\n\n\tmc := &Mongo{\n\t\tclient: instance,\n\t\tdb:     instance.Database(config.DatabaseName),\n\t\tconfig: config,\n\t}\n\n\tif mc.config.Locking.Enabled {\n\t\tif err := mc.ensureLockTable(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif err := mc.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn mc, nil\n}\n\nfunc (m *Mongo) Open(dsn string) (database.Driver, error) {\n\t// connstring is experimental package, but it used for parse connection string in mongo.Connect function\n\turi, err := connstring.Parse(dsn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(uri.Database) == 0 {\n\t\treturn nil, ErrNoDatabaseName\n\t}\n\tunknown := url.Values(uri.UnknownOptions)\n\n\tmigrationsCollection := unknown.Get(\"x-migrations-collection\")\n\tlockCollection := unknown.Get(\"x-advisory-lock-collection\")\n\ttransactionMode, err := parseBoolean(unknown.Get(\"x-transaction-mode\"), false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tadvisoryLockingFlag, err := parseBoolean(unknown.Get(\"x-advisory-locking\"), DefaultAdvisoryLockingFlag)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlockingTimout, err := parseInt(unknown.Get(\"x-advisory-lock-timeout\"), DefaultLockTimeout)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlockTimeoutIntervalValue := unknown.Get(\"x-advisory-lock-timeout-interval\")\n\t// The initial release had a typo for this argument but for backwards compatibility sake, we will keep supporting it\n\t// and we will error out if both values are set.\n\tlockTimeoutIntervalValueFromTypo := unknown.Get(\"x-advisory-lock-timout-interval\")\n\n\tlockTimeout := lockTimeoutIntervalValue\n\n\tif lockTimeoutIntervalValue != \"\" && lockTimeoutIntervalValueFromTypo != \"\" {\n\t\treturn nil, ErrLockTimeoutConfigConflict\n\t} else if lockTimeoutIntervalValueFromTypo != \"\" {\n\t\tlockTimeout = lockTimeoutIntervalValueFromTypo\n\t}\n\n\tmaxLockCheckInterval, err := parseInt(lockTimeout, DefaultLockTimeoutInterval)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tclient, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(dsn))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err = client.Ping(context.TODO(), nil); err != nil {\n\t\treturn nil, err\n\t}\n\tmc, err := WithInstance(client, &Config{\n\t\tDatabaseName:         uri.Database,\n\t\tMigrationsCollection: migrationsCollection,\n\t\tTransactionMode:      transactionMode,\n\t\tLocking: Locking{\n\t\t\tCollectionName: lockCollection,\n\t\t\tTimeout:        lockingTimout,\n\t\t\tEnabled:        advisoryLockingFlag,\n\t\t\tInterval:       maxLockCheckInterval,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn mc, nil\n}\n\n// Parse the url param, convert it to boolean\n// returns error if param invalid. returns defaultValue if param not present\nfunc parseBoolean(urlParam string, defaultValue bool) (bool, error) {\n\n\t// if parameter passed, parse it (otherwise return default value)\n\tif urlParam != \"\" {\n\t\tresult, err := strconv.ParseBool(urlParam)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\treturn result, nil\n\t}\n\n\t// if no url Param passed, return default value\n\treturn defaultValue, nil\n}\n\n// Parse the url param, convert it to int\n// returns error if param invalid. returns defaultValue if param not present\nfunc parseInt(urlParam string, defaultValue int) (int, error) {\n\n\t// if parameter passed, parse it (otherwise return default value)\n\tif urlParam != \"\" {\n\t\tresult, err := strconv.Atoi(urlParam)\n\t\tif err != nil {\n\t\t\treturn -1, err\n\t\t}\n\t\treturn result, nil\n\t}\n\n\t// if no url Param passed, return default value\n\treturn defaultValue, nil\n}\nfunc (m *Mongo) SetVersion(version int, dirty bool) error {\n\tmigrationsCollection := m.db.Collection(m.config.MigrationsCollection)\n\tif err := migrationsCollection.Drop(context.TODO()); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"drop migrations collection failed\"}\n\t}\n\t_, err := migrationsCollection.InsertOne(context.TODO(), bson.M{\"version\": version, \"dirty\": dirty})\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"save version failed\"}\n\t}\n\treturn nil\n}\n\nfunc (m *Mongo) Version() (version int, dirty bool, err error) {\n\tvar versionInfo versionInfo\n\terr = m.db.Collection(m.config.MigrationsCollection).FindOne(context.TODO(), bson.M{}).Decode(&versionInfo)\n\tswitch {\n\tcase err == mongo.ErrNoDocuments:\n\t\treturn database.NilVersion, false, nil\n\tcase err != nil:\n\t\treturn 0, false, &database.Error{OrigErr: err, Err: \"failed to get migration version\"}\n\tdefault:\n\t\treturn versionInfo.Version, versionInfo.Dirty, nil\n\t}\n}\n\nfunc (m *Mongo) Run(migration io.Reader) error {\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar cmds []bson.D\n\terr = bson.UnmarshalExtJSON(migr, true, &cmds)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unmarshaling json error: %s\", err)\n\t}\n\tif m.config.TransactionMode {\n\t\tif err := m.executeCommandsWithTransaction(context.TODO(), cmds); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tif err := m.executeCommands(context.TODO(), cmds); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (m *Mongo) executeCommandsWithTransaction(ctx context.Context, cmds []bson.D) error {\n\terr := m.db.Client().UseSession(ctx, func(sessionContext mongo.SessionContext) error {\n\t\tif err := sessionContext.StartTransaction(); err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Err: \"failed to start transaction\"}\n\t\t}\n\t\tif err := m.executeCommands(sessionContext, cmds); err != nil {\n\t\t\t// When command execution is failed, it's aborting transaction\n\t\t\t// If you tried to call abortTransaction, it`s return error that transaction already aborted\n\t\t\treturn err\n\t\t}\n\t\tif err := sessionContext.CommitTransaction(sessionContext); err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Err: \"failed to commit transaction\"}\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (m *Mongo) executeCommands(ctx context.Context, cmds []bson.D) error {\n\tfor _, cmd := range cmds {\n\t\terr := m.db.RunCommand(ctx, cmd).Err()\n\t\tif err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Err: fmt.Sprintf(\"failed to execute command:%v\", cmd)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (m *Mongo) Close() error {\n\treturn m.client.Disconnect(context.TODO())\n}\n\nfunc (m *Mongo) Drop() error {\n\treturn m.db.Drop(context.TODO())\n}\n\nfunc (m *Mongo) ensureLockTable() error {\n\tindexes := m.db.Collection(m.config.Locking.CollectionName).Indexes()\n\n\tindexOptions := options.Index().SetUnique(true).SetName(LockIndexName)\n\t_, err := indexes.CreateOne(context.TODO(), mongo.IndexModel{\n\t\tOptions: indexOptions,\n\t\tKeys:    findFilter{Key: -1},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database, which deviates from the usual\n// convention of \"caller locks\" in the MongoDb type.\nfunc (m *Mongo) ensureVersionTable() (err error) {\n\tif err = m.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := m.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\tif err != nil {\n\t\treturn err\n\t}\n\tif _, _, err = m.Version(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// Utilizes advisory locking on the config.LockingCollection collection\n// This uses a unique index on the `locking_key` field.\nfunc (m *Mongo) Lock() error {\n\treturn database.CasRestoreOnErr(&m.isLocked, false, true, database.ErrLocked, func() error {\n\t\tif !m.config.Locking.Enabled {\n\t\t\treturn nil\n\t\t}\n\n\t\tpid := os.Getpid()\n\t\thostname, err := os.Hostname()\n\t\tif err != nil {\n\t\t\thostname = fmt.Sprintf(\"Could not determine hostname. Error: %s\", err.Error())\n\t\t}\n\n\t\tnewLockObj := lockObj{\n\t\t\tKey:       lockKeyUniqueValue,\n\t\t\tPid:       pid,\n\t\t\tHostname:  hostname,\n\t\t\tCreatedAt: time.Now(),\n\t\t}\n\t\toperation := func() error {\n\t\t\ttimeout, cancelFunc := context.WithTimeout(context.Background(), contextWaitTimeout)\n\t\t\t_, err := m.db.Collection(m.config.Locking.CollectionName).InsertOne(timeout, newLockObj)\n\t\t\tdefer cancelFunc()\n\t\t\treturn err\n\t\t}\n\t\texponentialBackOff := backoff.NewExponentialBackOff()\n\t\tduration := time.Duration(m.config.Locking.Timeout) * time.Second\n\t\texponentialBackOff.MaxElapsedTime = duration\n\t\texponentialBackOff.MaxInterval = time.Duration(m.config.Locking.Interval) * time.Second\n\n\t\terr = backoff.Retry(operation, exponentialBackOff)\n\t\tif err != nil {\n\t\t\treturn database.ErrLocked\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc (m *Mongo) Unlock() error {\n\treturn database.CasRestoreOnErr(&m.isLocked, true, false, database.ErrNotLocked, func() error {\n\t\tif !m.config.Locking.Enabled {\n\t\t\treturn nil\n\t\t}\n\n\t\tfilter := findFilter{\n\t\t\tKey: lockKeyUniqueValue,\n\t\t}\n\n\t\tctx, cancel := context.WithTimeout(context.Background(), contextWaitTimeout)\n\t\t_, err := m.db.Collection(m.config.Locking.CollectionName).DeleteMany(ctx, filter)\n\t\tdefer cancel()\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "database/mongodb/mongodb_test.go",
    "content": "package mongodb\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\n\t\"log\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"io\"\n\t\"os\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n)\n\nimport (\n\t\"github.com/dhui/dktest\"\n\t\"go.mongodb.org/mongo-driver/bson\"\n\t\"go.mongodb.org/mongo-driver/mongo\"\n\t\"go.mongodb.org/mongo-driver/mongo/options\"\n)\n\nimport (\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t\"github.com/golang-migrate/migrate/v4/dktesting\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nvar (\n\topts = dktest.Options{PortRequired: true, ReadyFunc: isReady}\n\t// Supported versions: https://www.mongodb.com/support-policy\n\tspecs = []dktesting.ContainerSpec{\n\t\t{ImageName: \"mongo:5.0\", Options: opts},\n\t\t{ImageName: \"mongo:6.0\", Options: opts},\n\t\t{ImageName: \"mongo:7.0\", Options: opts},\n\t\t{ImageName: \"mongo:8.0\", Options: opts},\n\t}\n)\n\nfunc mongoConnectionString(host, port string) string {\n\t// there is connect option for excluding serverConnection algorithm\n\t// it's let avoid errors with mongo replica set connection in docker container\n\treturn fmt.Sprintf(\"mongodb://%s:%s/testMigration?connect=direct\", host, port)\n}\n\nfunc isReady(ctx context.Context, c dktest.ContainerInfo) bool {\n\tip, port, err := c.FirstPort()\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tclient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoConnectionString(ip, port)))\n\tif err != nil {\n\t\treturn false\n\t}\n\tdefer func() {\n\t\tif err := client.Disconnect(ctx); err != nil {\n\t\t\tlog.Println(\"close error:\", err)\n\t\t}\n\t}()\n\n\tif err = client.Ping(ctx, nil); err != nil {\n\t\tswitch err {\n\t\tcase io.EOF:\n\t\t\treturn false\n\t\tdefault:\n\t\t\tlog.Println(err)\n\t\t}\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc Test(t *testing.T) {\n\tt.Run(\"test\", test)\n\tt.Run(\"testMigrate\", testMigrate)\n\tt.Run(\"testWithAuth\", testWithAuth)\n\tt.Run(\"testLockWorks\", testLockWorks)\n\n\tt.Cleanup(func() {\n\t\tfor _, spec := range specs {\n\t\t\tt.Log(\"Cleaning up \", spec.ImageName)\n\t\t\tif err := spec.Cleanup(); err != nil {\n\t\t\t\tt.Error(\"Error removing \", spec.ImageName, \"error:\", err)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc test(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := mongoConnectionString(ip, port)\n\t\tp := &Mongo{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tdt.TestNilVersion(t, d)\n\t\tdt.TestLockAndUnlock(t, d)\n\t\tdt.TestRun(t, d, bytes.NewReader([]byte(`[{\"insert\":\"hello\",\"documents\":[{\"wild\":\"world\"}]}]`)))\n\t\tdt.TestSetVersion(t, d)\n\t\tdt.TestDrop(t, d)\n\t})\n}\n\nfunc testMigrate(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := mongoConnectionString(ip, port)\n\t\tp := &Mongo{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://./examples/migrations\", \"\", d)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\t})\n}\n\nfunc testWithAuth(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := mongoConnectionString(ip, port)\n\t\tp := &Mongo{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tcreateUserCMD := []byte(`[{\"createUser\":\"deminem\",\"pwd\":\"gogo\",\"roles\":[{\"role\":\"readWrite\",\"db\":\"testMigration\"}]}]`)\n\t\terr = d.Run(bytes.NewReader(createUserCMD))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\ttestcases := []struct {\n\t\t\tname            string\n\t\t\tconnectUri      string\n\t\t\tisErrorExpected bool\n\t\t}{\n\t\t\t{\"right auth data\", \"mongodb://deminem:gogo@%s:%v/testMigration\", false},\n\t\t\t{\"wrong auth data\", \"mongodb://wrong:auth@%s:%v/testMigration\", true},\n\t\t}\n\n\t\tfor _, tcase := range testcases {\n\t\t\tt.Run(tcase.name, func(t *testing.T) {\n\t\t\t\tmc := &Mongo{}\n\t\t\t\td, err := mc.Open(fmt.Sprintf(tcase.connectUri, ip, port))\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer func() {\n\t\t\t\t\t\tif err := d.Close(); err != nil {\n\t\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t\t}\n\t\t\t\t\t}()\n\t\t\t\t}\n\n\t\t\t\tswitch {\n\t\t\t\tcase tcase.isErrorExpected && err == nil:\n\t\t\t\t\tt.Fatalf(\"no error when expected\")\n\t\t\t\tcase !tcase.isErrorExpected && err != nil:\n\t\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc testLockWorks(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := mongoConnectionString(ip, port)\n\t\tp := &Mongo{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tdt.TestRun(t, d, bytes.NewReader([]byte(`[{\"insert\":\"hello\",\"documents\":[{\"wild\":\"world\"}]}]`)))\n\n\t\tmc := d.(*Mongo)\n\n\t\terr = mc.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\terr = mc.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = mc.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\terr = mc.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// enable locking,\n\t\t//try to hit a lock conflict\n\t\tmc.config.Locking.Enabled = true\n\t\tmc.config.Locking.Timeout = 1\n\t\terr = mc.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\terr = mc.Lock()\n\t\tif err == nil {\n\t\t\tt.Fatal(\"should have failed, mongo should be locked already\")\n\t\t}\n\t})\n}\n\nfunc TestTransaction(t *testing.T) {\n\ttransactionSpecs := []dktesting.ContainerSpec{\n\t\t{ImageName: \"mongo:4\", Options: dktest.Options{PortRequired: true, ReadyFunc: isReady,\n\t\t\tCmd: []string{\"mongod\", \"--bind_ip_all\", \"--replSet\", \"rs0\"}}},\n\t}\n\tt.Cleanup(func() {\n\t\tfor _, spec := range transactionSpecs {\n\t\t\tt.Log(\"Cleaning up \", spec.ImageName)\n\t\t\tif err := spec.Cleanup(); err != nil {\n\t\t\t\tt.Error(\"Error removing \", spec.ImageName, \"error:\", err)\n\t\t\t}\n\t\t}\n\t})\n\n\tdktesting.ParallelTest(t, transactionSpecs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tclient, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(mongoConnectionString(ip, port)))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\terr = client.Ping(context.TODO(), nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\t//rs.initiate()\n\t\terr = client.Database(\"admin\").RunCommand(context.TODO(), bson.D{bson.E{Key: \"replSetInitiate\", Value: bson.D{}}}).Err()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\terr = waitForReplicaInit(client)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\td, err := WithInstance(client, &Config{\n\t\t\tDatabaseName: \"testMigration\",\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\t//We have to create collection\n\t\t//transactions don't support operations with creating new dbs, collections\n\t\t//Unique index need for checking transaction aborting\n\t\tinsertCMD := []byte(`[\n\t\t\t\t{\"create\":\"hello\"},\n\t\t\t\t{\"createIndexes\": \"hello\",\n\t\t\t\t\t\"indexes\": [{\n\t\t\t\t\t\t\"key\": {\n\t\t\t\t\t\t\t\"wild\": 1\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"name\": \"unique_wild\",\n\t\t\t\t\t\t\"unique\": true,\n\t\t\t\t\t\t\"background\": true\n\t\t\t\t\t}]\n\t\t\t}]`)\n\t\terr = d.Run(bytes.NewReader(insertCMD))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\ttestcases := []struct {\n\t\t\tname            string\n\t\t\tcmds            []byte\n\t\t\tdocumentsCount  int64\n\t\t\tisErrorExpected bool\n\t\t}{\n\t\t\t{\n\t\t\t\tname: \"success transaction\",\n\t\t\t\tcmds: []byte(`[{\"insert\":\"hello\",\"documents\":[\n\t\t\t\t\t\t\t\t\t\t{\"wild\":\"world\"},\n\t\t\t\t\t\t\t\t\t\t{\"wild\":\"west\"},\n\t\t\t\t\t\t\t\t\t\t{\"wild\":\"natural\"}\n\t\t\t\t\t\t\t\t\t ]\n\t\t\t\t\t\t\t\t  }]`),\n\t\t\t\tdocumentsCount:  3,\n\t\t\t\tisErrorExpected: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"failure transaction\",\n\t\t\t\t//transaction have to be failure - duplicate unique key wild:west\n\t\t\t\t//none of the documents should be added\n\t\t\t\tcmds: []byte(`[{\"insert\":\"hello\",\"documents\":[{\"wild\":\"flower\"}]},\n\t\t\t\t\t\t\t\t\t{\"insert\":\"hello\",\"documents\":[\n\t\t\t\t\t\t\t\t\t\t{\"wild\":\"cat\"},\n\t\t\t\t\t\t\t\t\t\t{\"wild\":\"west\"}\n\t\t\t\t\t\t\t\t\t ]\n\t\t\t\t\t\t\t\t  }]`),\n\t\t\t\tdocumentsCount:  3,\n\t\t\t\tisErrorExpected: true,\n\t\t\t},\n\t\t}\n\t\tfor _, tcase := range testcases {\n\t\t\tt.Run(tcase.name, func(t *testing.T) {\n\t\t\t\tclient, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(mongoConnectionString(ip, port)))\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\terr = client.Ping(context.TODO(), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\td, err := WithInstance(client, &Config{\n\t\t\t\t\tDatabaseName:    \"testMigration\",\n\t\t\t\t\tTransactionMode: true,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\tdefer func() {\n\t\t\t\t\tif err := d.Close(); err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\trunErr := d.Run(bytes.NewReader(tcase.cmds))\n\t\t\t\tif runErr != nil {\n\t\t\t\t\tif !tcase.isErrorExpected {\n\t\t\t\t\t\tt.Fatal(runErr)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdocumentsCount, err := client.Database(\"testMigration\").Collection(\"hello\").CountDocuments(context.TODO(), bson.M{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\tif tcase.documentsCount != documentsCount {\n\t\t\t\t\tt.Fatalf(\"expected %d and actual %d documents count not equal. run migration error:%s\", tcase.documentsCount, documentsCount, runErr)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\ntype isMaster struct {\n\tIsMaster bool `bson:\"ismaster\"`\n}\n\nfunc waitForReplicaInit(client *mongo.Client) error {\n\tticker := time.NewTicker(time.Second * 1)\n\tdefer ticker.Stop()\n\ttimeout, err := strconv.Atoi(os.Getenv(\"MIGRATE_TEST_MONGO_REPLICA_SET_INIT_TIMEOUT\"))\n\tif err != nil {\n\t\ttimeout = 30\n\t}\n\ttimeoutTimer := time.NewTimer(time.Duration(timeout) * time.Second)\n\tdefer timeoutTimer.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tvar status isMaster\n\t\t\t//Check that node is primary because\n\t\t\t//during replica set initialization, the first node first becomes a secondary and then becomes the primary\n\t\t\t//should consider that initialization is completed only after the node has become the primary\n\t\t\tresult := client.Database(\"admin\").RunCommand(context.TODO(), bson.D{bson.E{Key: \"isMaster\", Value: 1}})\n\t\t\tr, err := result.DecodeBytes()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = bson.Unmarshal(r, &status)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif status.IsMaster {\n\t\t\t\treturn nil\n\t\t\t}\n\t\tcase <-timeoutTimer.C:\n\t\t\treturn fmt.Errorf(\"replica init timeout\")\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "database/multistmt/parse.go",
    "content": "// Package multistmt provides methods for parsing multi-statement database migrations\npackage multistmt\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n)\n\n// StartBufSize is the default starting size of the buffer used to scan and parse multi-statement migrations\nvar StartBufSize = 4096\n\n// Handler handles a single migration parsed from a multi-statement migration.\n// It's given the single migration to handle and returns whether or not further statements\n// from the multi-statement migration should be parsed and handled.\ntype Handler func(migration []byte) bool\n\nfunc splitWithDelimiter(delimiter []byte) func(d []byte, atEOF bool) (int, []byte, error) {\n\treturn func(d []byte, atEOF bool) (int, []byte, error) {\n\t\t// SplitFunc inspired by bufio.ScanLines() implementation\n\t\tif atEOF {\n\t\t\tif len(d) == 0 {\n\t\t\t\treturn 0, nil, nil\n\t\t\t}\n\t\t\treturn len(d), d, nil\n\t\t}\n\t\tif i := bytes.Index(d, delimiter); i >= 0 {\n\t\t\treturn i + len(delimiter), d[:i+len(delimiter)], nil\n\t\t}\n\t\treturn 0, nil, nil\n\t}\n}\n\n// Parse parses the given multi-statement migration\nfunc Parse(reader io.Reader, delimiter []byte, maxMigrationSize int, h Handler) error {\n\tscanner := bufio.NewScanner(reader)\n\tscanner.Buffer(make([]byte, 0, StartBufSize), maxMigrationSize)\n\tscanner.Split(splitWithDelimiter(delimiter))\n\tfor scanner.Scan() {\n\t\tcont := h(scanner.Bytes())\n\t\tif !cont {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn scanner.Err()\n}\n"
  },
  {
    "path": "database/multistmt/parse_test.go",
    "content": "package multistmt_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/golang-migrate/migrate/v4/database/multistmt\"\n)\n\nconst maxMigrationSize = 1024\n\nfunc TestParse(t *testing.T) {\n\ttestCases := []struct {\n\t\tname        string\n\t\tmultiStmt   string\n\t\tdelimiter   string\n\t\texpected    []string\n\t\texpectedErr error\n\t}{\n\t\t{name: \"single statement, no delimiter\", multiStmt: \"single statement, no delimiter\", delimiter: \";\",\n\t\t\texpected: []string{\"single statement, no delimiter\"}, expectedErr: nil},\n\t\t{name: \"single statement, one delimiter\", multiStmt: \"single statement, one delimiter;\", delimiter: \";\",\n\t\t\texpected: []string{\"single statement, one delimiter;\"}, expectedErr: nil},\n\t\t{name: \"two statements, no trailing delimiter\", multiStmt: \"statement one; statement two\", delimiter: \";\",\n\t\t\texpected: []string{\"statement one;\", \" statement two\"}, expectedErr: nil},\n\t\t{name: \"two statements, with trailing delimiter\", multiStmt: \"statement one; statement two;\", delimiter: \";\",\n\t\t\texpected: []string{\"statement one;\", \" statement two;\"}, expectedErr: nil},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tstmts := make([]string, 0, len(tc.expected))\n\t\t\terr := multistmt.Parse(strings.NewReader(tc.multiStmt), []byte(tc.delimiter), maxMigrationSize, func(b []byte) bool {\n\t\t\t\tstmts = append(stmts, string(b))\n\t\t\t\treturn true\n\t\t\t})\n\t\t\tassert.Equal(t, tc.expectedErr, err)\n\t\t\tassert.Equal(t, tc.expected, stmts)\n\t\t})\n\t}\n}\n\nfunc TestParseDiscontinue(t *testing.T) {\n\tmultiStmt := \"statement one; statement two\"\n\tdelimiter := \";\"\n\texpected := []string{\"statement one;\"}\n\n\tstmts := make([]string, 0, len(expected))\n\terr := multistmt.Parse(strings.NewReader(multiStmt), []byte(delimiter), maxMigrationSize, func(b []byte) bool {\n\t\tstmts = append(stmts, string(b))\n\t\treturn false\n\t})\n\tassert.Nil(t, err)\n\tassert.Equal(t, expected, stmts)\n}\n"
  },
  {
    "path": "database/mysql/README.md",
    "content": "# MySQL\n\n`mysql://user:password@tcp(host:port)/dbname?query`\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |\n| `x-no-lock` | `NoLock` | Set to `true` to skip `GET_LOCK`/`RELEASE_LOCK` statements. Useful for [multi-master MySQL flavors](https://www.percona.com/doc/percona-xtradb-cluster/LATEST/features/pxc-strict-mode.html#explicit-table-locking). Only run migrations from one host when this is enabled. |\n| `x-statement-timeout` | `StatementTimeout` | Abort any statement that takes more than the specified number of milliseconds, functionally similar to [Server-side SELECT statement timeouts](https://dev.mysql.com/blog-archive/server-side-select-statement-timeouts/) but enforced by the client. Available for all versions of MySQL, not just >=5.7. | \n| `dbname` | `DatabaseName` | The name of the database to connect to |\n| `user` | | The user to sign in as |\n| `password` | | The user's password | \n| `host` | | The host to connect to. |\n| `port` | | The port to bind to. |\n| `tls`  | | TLS / SSL encrypted connection parameter; see [go-sql-driver](https://github.com/go-sql-driver/mysql#tls). Use any name (e.g. `migrate`) if you want to use a custom TLS config (`x-tls-` queries). |\n| `x-tls-ca` | | The location of the CA (certificate authority) file. |\n| `x-tls-cert` | | The location of the client certificate file. Must be used with `x-tls-key`. |\n| `x-tls-key` | | The location of the private key file. Must be used with `x-tls-cert`. |\n| `x-tls-insecure-skip-verify` | | Whether or not to use SSL (true\\|false) | \n\n## Use with existing client\n\nIf you use the MySQL driver with existing database client, you must create the client with parameter `multiStatements=true`:\n\n```go\npackage main\n\nimport (\n    \"database/sql\"\n    \n    _ \"github.com/go-sql-driver/mysql\"\n    \"github.com/golang-migrate/migrate/v4\"\n    \"github.com/golang-migrate/migrate/v4/database/mysql\"\n    _ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nfunc main() {\n    db, _ := sql.Open(\"mysql\", \"user:password@tcp(host:port)/dbname?multiStatements=true\")\n    driver, _ := mysql.WithInstance(db, &mysql.Config{})\n    m, _ := migrate.NewWithDatabaseInstance(\n        \"file:///migrations\",\n        \"mysql\", \n        driver,\n    )\n    \n    m.Steps(2)\n}\n```\n\n## Upgrading from v1\n\n1. Write down the current migration version from schema_migrations\n1. `DROP TABLE schema_migrations`\n2. Wrap your existing migrations in transactions ([BEGIN/COMMIT](https://dev.mysql.com/doc/refman/5.7/en/commit.html)) if you use multiple statements within one migration.\n3. Download and install the latest migrate version.\n4. Force the current migration version with `migrate force <current_version>`.\n"
  },
  {
    "path": "database/mysql/examples/migrations/1_init.down.sql",
    "content": "DROP TABLE IF EXISTS test;"
  },
  {
    "path": "database/mysql/examples/migrations/1_init.up.sql",
    "content": "CREATE TABLE IF NOT EXISTS test (\n  firstname VARCHAR(16)\n);"
  },
  {
    "path": "database/mysql/mysql.go",
    "content": "//go:build go1.9\n\npackage mysql\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/go-sql-driver/mysql\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n)\n\nvar _ database.Driver = (*Mysql)(nil) // explicit compile time type check\n\nfunc init() {\n\tdatabase.Register(\"mysql\", &Mysql{})\n}\n\nvar DefaultMigrationsTable = \"schema_migrations\"\n\nvar (\n\tErrDatabaseDirty    = fmt.Errorf(\"database is dirty\")\n\tErrNilConfig        = fmt.Errorf(\"no config\")\n\tErrNoDatabaseName   = fmt.Errorf(\"no database name\")\n\tErrAppendPEM        = fmt.Errorf(\"failed to append PEM\")\n\tErrTLSCertKeyConfig = fmt.Errorf(\"to use TLS client authentication, both x-tls-cert and x-tls-key must not be empty\")\n)\n\ntype Config struct {\n\tMigrationsTable  string\n\tDatabaseName     string\n\tNoLock           bool\n\tStatementTimeout time.Duration\n}\n\ntype Mysql struct {\n\t// mysql RELEASE_LOCK must be called from the same conn, so\n\t// just do everything over a single conn anyway.\n\tconn     *sql.Conn\n\tdb       *sql.DB\n\tisLocked atomic.Bool\n\n\tconfig *Config\n}\n\n// connection instance must have `multiStatements` set to true\nfunc WithConnection(ctx context.Context, conn *sql.Conn, config *Config) (*Mysql, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif err := conn.PingContext(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\n\tmx := &Mysql{\n\t\tconn:   conn,\n\t\tdb:     nil,\n\t\tconfig: config,\n\t}\n\n\tif config.DatabaseName == \"\" {\n\t\tquery := `SELECT DATABASE()`\n\t\tvar databaseName sql.NullString\n\t\tif err := conn.QueryRowContext(ctx, query).Scan(&databaseName); err != nil {\n\t\t\treturn nil, &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\tif len(databaseName.String) == 0 {\n\t\t\treturn nil, ErrNoDatabaseName\n\t\t}\n\n\t\tconfig.DatabaseName = databaseName.String\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tif err := mx.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn mx, nil\n}\n\n// instance must have `multiStatements` set to true\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {\n\tctx := context.Background()\n\n\tif err := instance.Ping(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tconn, err := instance.Conn(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmx, err := WithConnection(ctx, conn, config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmx.db = instance\n\n\treturn mx, nil\n}\n\n// extractCustomQueryParams extracts the custom query params (ones that start with \"x-\") from\n// mysql.Config.Params (connection parameters) as to not interfere with connecting to MySQL\nfunc extractCustomQueryParams(c *mysql.Config) (map[string]string, error) {\n\tif c == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\tcustomQueryParams := map[string]string{}\n\n\tfor k, v := range c.Params {\n\t\tif strings.HasPrefix(k, \"x-\") {\n\t\t\tcustomQueryParams[k] = v\n\t\t\tdelete(c.Params, k)\n\t\t}\n\t}\n\treturn customQueryParams, nil\n}\n\nfunc urlToMySQLConfig(url string) (*mysql.Config, error) {\n\t// Need to parse out custom TLS parameters and call\n\t// mysql.RegisterTLSConfig() before mysql.ParseDSN() is called\n\t// which consumes the registered tls.Config\n\t// Fixes: https://github.com/golang-migrate/migrate/issues/411\n\t//\n\t// Can't use url.Parse() since it fails to parse MySQL DSNs\n\t// mysql.ParseDSN() also searches for \"?\" to find query parameters:\n\t// https://github.com/go-sql-driver/mysql/blob/46351a8/dsn.go#L344\n\tif idx := strings.LastIndex(url, \"?\"); idx > 0 {\n\t\trawParams := url[idx+1:]\n\t\tparsedParams, err := nurl.ParseQuery(rawParams)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tctls := parsedParams.Get(\"tls\")\n\t\tif len(ctls) > 0 {\n\t\t\tif _, isBool := readBool(ctls); !isBool && strings.ToLower(ctls) != \"skip-verify\" {\n\t\t\t\trootCertPool := x509.NewCertPool()\n\t\t\t\tpem, err := os.ReadFile(parsedParams.Get(\"x-tls-ca\"))\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 ok := rootCertPool.AppendCertsFromPEM(pem); !ok {\n\t\t\t\t\treturn nil, ErrAppendPEM\n\t\t\t\t}\n\n\t\t\t\tclientCert := make([]tls.Certificate, 0, 1)\n\t\t\t\tif ccert, ckey := parsedParams.Get(\"x-tls-cert\"), parsedParams.Get(\"x-tls-key\"); ccert != \"\" || ckey != \"\" {\n\t\t\t\t\tif ccert == \"\" || ckey == \"\" {\n\t\t\t\t\t\treturn nil, ErrTLSCertKeyConfig\n\t\t\t\t\t}\n\t\t\t\t\tcerts, err := tls.LoadX509KeyPair(ccert, ckey)\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\tclientCert = append(clientCert, certs)\n\t\t\t\t}\n\n\t\t\t\tinsecureSkipVerify := false\n\t\t\t\tinsecureSkipVerifyStr := parsedParams.Get(\"x-tls-insecure-skip-verify\")\n\t\t\t\tif len(insecureSkipVerifyStr) > 0 {\n\t\t\t\t\tx, err := strconv.ParseBool(insecureSkipVerifyStr)\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\tinsecureSkipVerify = x\n\t\t\t\t}\n\n\t\t\t\terr = mysql.RegisterTLSConfig(ctls, &tls.Config{\n\t\t\t\t\tRootCAs:            rootCertPool,\n\t\t\t\t\tCertificates:       clientCert,\n\t\t\t\t\tInsecureSkipVerify: insecureSkipVerify,\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\t\t\t}\n\t\t}\n\t}\n\n\tconfig, err := mysql.ParseDSN(strings.TrimPrefix(url, \"mysql://\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tconfig.MultiStatements = true\n\n\t// Keep backwards compatibility from when we used net/url.Parse() to parse the DSN.\n\t// net/url.Parse() would automatically unescape it for us.\n\t// See: https://play.golang.org/p/q9j1io-YICQ\n\tuser, err := nurl.QueryUnescape(config.User)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconfig.User = user\n\n\tpassword, err := nurl.QueryUnescape(config.Passwd)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconfig.Passwd = password\n\n\treturn config, nil\n}\n\nfunc (m *Mysql) Open(url string) (database.Driver, error) {\n\tconfig, err := urlToMySQLConfig(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcustomParams, err := extractCustomQueryParams(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnoLockParam, noLock := customParams[\"x-no-lock\"], false\n\tif noLockParam != \"\" {\n\t\tnoLock, err = strconv.ParseBool(noLockParam)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not parse x-no-lock as bool: %w\", err)\n\t\t}\n\t}\n\n\tstatementTimeoutParam := customParams[\"x-statement-timeout\"]\n\tstatementTimeout := 0\n\tif statementTimeoutParam != \"\" {\n\t\tstatementTimeout, err = strconv.Atoi(statementTimeoutParam)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not parse x-statement-timeout as float: %w\", err)\n\t\t}\n\t}\n\n\tdb, err := sql.Open(\"mysql\", config.FormatDSN())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmx, err := WithInstance(db, &Config{\n\t\tDatabaseName:     config.DBName,\n\t\tMigrationsTable:  customParams[\"x-migrations-table\"],\n\t\tNoLock:           noLock,\n\t\tStatementTimeout: time.Duration(statementTimeout) * time.Millisecond,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn mx, nil\n}\n\nfunc (m *Mysql) Close() error {\n\tconnErr := m.conn.Close()\n\tvar dbErr error\n\tif m.db != nil {\n\t\tdbErr = m.db.Close()\n\t}\n\n\tif connErr != nil || dbErr != nil {\n\t\treturn fmt.Errorf(\"conn: %v, db: %v\", connErr, dbErr)\n\t}\n\treturn nil\n}\n\nfunc (m *Mysql) Lock() error {\n\treturn database.CasRestoreOnErr(&m.isLocked, false, true, database.ErrLocked, func() error {\n\t\tif m.config.NoLock {\n\t\t\treturn nil\n\t\t}\n\t\taid, err := database.GenerateAdvisoryLockId(\n\t\t\tfmt.Sprintf(\"%s:%s\", m.config.DatabaseName, m.config.MigrationsTable))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tquery := \"SELECT GET_LOCK(?, 10)\"\n\t\tvar success bool\n\t\tif err := m.conn.QueryRowContext(context.Background(), query, aid).Scan(&success); err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Err: \"try lock failed\", Query: []byte(query)}\n\t\t}\n\n\t\tif !success {\n\t\t\treturn database.ErrLocked\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc (m *Mysql) Unlock() error {\n\treturn database.CasRestoreOnErr(&m.isLocked, true, false, database.ErrNotLocked, func() error {\n\t\tif m.config.NoLock {\n\t\t\treturn nil\n\t\t}\n\n\t\taid, err := database.GenerateAdvisoryLockId(\n\t\t\tfmt.Sprintf(\"%s:%s\", m.config.DatabaseName, m.config.MigrationsTable))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tquery := `SELECT RELEASE_LOCK(?)`\n\t\tif _, err := m.conn.ExecContext(context.Background(), query, aid); err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\t// NOTE: RELEASE_LOCK could return NULL or (or 0 if the code is changed),\n\t\t// in which case isLocked should be true until the timeout expires -- synchronizing\n\t\t// these states is likely not worth trying to do; reconsider the necessity of isLocked.\n\n\t\treturn nil\n\t})\n}\n\nfunc (m *Mysql) Run(migration io.Reader) error {\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tctx := context.Background()\n\tif m.config.StatementTimeout != 0 {\n\t\tvar cancel context.CancelFunc\n\t\tctx, cancel = context.WithTimeout(ctx, m.config.StatementTimeout)\n\t\tdefer cancel()\n\t}\n\n\tquery := string(migr[:])\n\tif _, err := m.conn.ExecContext(ctx, query); err != nil {\n\t\treturn database.Error{OrigErr: err, Err: \"migration failed\", Query: migr}\n\t}\n\n\treturn nil\n}\n\nfunc (m *Mysql) SetVersion(version int, dirty bool) error {\n\ttx, err := m.conn.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelSerializable})\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\n\tquery := \"DELETE FROM `\" + m.config.MigrationsTable + \"` LIMIT 1\"\n\tif _, err := tx.ExecContext(context.Background(), query); err != nil {\n\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\terr = errors.Join(err, errRollback)\n\t\t}\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\t// Also re-write the schema version for nil dirty versions to prevent\n\t// empty schema version for failed down migration on the first migration\n\t// See: https://github.com/golang-migrate/migrate/issues/330\n\tif version >= 0 || (version == database.NilVersion && dirty) {\n\t\tquery := \"INSERT INTO `\" + m.config.MigrationsTable + \"` (version, dirty) VALUES (?, ?)\"\n\t\tif _, err := tx.ExecContext(context.Background(), query, version, dirty); err != nil {\n\t\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\t\terr = errors.Join(err, errRollback)\n\t\t\t}\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction commit failed\"}\n\t}\n\n\treturn nil\n}\n\nfunc (m *Mysql) Version() (version int, dirty bool, err error) {\n\tquery := \"SELECT version, dirty FROM `\" + m.config.MigrationsTable + \"` LIMIT 1\"\n\terr = m.conn.QueryRowContext(context.Background(), query).Scan(&version, &dirty)\n\tswitch {\n\tcase err == sql.ErrNoRows:\n\t\treturn database.NilVersion, false, nil\n\n\tcase err != nil:\n\t\tif e, ok := err.(*mysql.MySQLError); ok {\n\t\t\tif e.Number == 0 {\n\t\t\t\treturn database.NilVersion, false, nil\n\t\t\t}\n\t\t}\n\t\treturn 0, false, &database.Error{OrigErr: err, Query: []byte(query)}\n\n\tdefault:\n\t\treturn version, dirty, nil\n\t}\n}\n\nfunc (m *Mysql) Drop() (err error) {\n\t// select all tables\n\tquery := `SHOW TABLES LIKE '%'`\n\ttables, err := m.conn.QueryContext(context.Background(), query)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tdefer func() {\n\t\tif errClose := tables.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\t// delete one table after another\n\ttableNames := make([]string, 0)\n\tfor tables.Next() {\n\t\tvar tableName string\n\t\tif err := tables.Scan(&tableName); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(tableName) > 0 {\n\t\t\ttableNames = append(tableNames, tableName)\n\t\t}\n\t}\n\tif err := tables.Err(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tif len(tableNames) > 0 {\n\t\t// disable checking foreign key constraints until finished\n\t\tquery = `SET foreign_key_checks = 0`\n\t\tif _, err := m.conn.ExecContext(context.Background(), query); err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\tdefer func() {\n\t\t\t// enable foreign key checks\n\t\t\t_, _ = m.conn.ExecContext(context.Background(), `SET foreign_key_checks = 1`)\n\t\t}()\n\n\t\t// delete one by one ...\n\t\tfor _, t := range tableNames {\n\t\t\tquery = \"DROP TABLE IF EXISTS `\" + t + \"`\"\n\t\t\tif _, err := m.conn.ExecContext(context.Background(), query); err != nil {\n\t\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database, which deviates from the usual\n// convention of \"caller locks\" in the Mysql type.\nfunc (m *Mysql) ensureVersionTable() (err error) {\n\tif err = m.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := m.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\t// check if migration table exists\n\tvar result string\n\tquery := `SHOW TABLES LIKE '` + m.config.MigrationsTable + `'`\n\tif err := m.conn.QueryRowContext(context.Background(), query).Scan(&result); err != nil {\n\t\tif err != sql.ErrNoRows {\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t} else {\n\t\treturn nil\n\t}\n\n\t// if not, create the empty migration table\n\tquery = \"CREATE TABLE `\" + m.config.MigrationsTable + \"` (version bigint not null primary key, dirty boolean not null)\"\n\tif _, err := m.conn.ExecContext(context.Background(), query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\treturn nil\n}\n\n// Returns the bool value of the input.\n// The 2nd return value indicates if the input was a valid bool value\n// See https://github.com/go-sql-driver/mysql/blob/a059889267dc7170331388008528b3b44479bffb/utils.go#L71\nfunc readBool(input string) (value bool, valid bool) {\n\tswitch input {\n\tcase \"1\", \"true\", \"TRUE\", \"True\":\n\t\treturn true, true\n\tcase \"0\", \"false\", \"FALSE\", \"False\":\n\t\treturn false, true\n\t}\n\n\t// Not a valid bool value\n\treturn\n}\n"
  },
  {
    "path": "database/mysql/mysql_test.go",
    "content": "package mysql\n\nimport (\n\t\"context\"\n\t\"crypto/ed25519\"\n\t\"crypto/x509\"\n\t\"database/sql\"\n\tsqldriver \"database/sql/driver\"\n\t\"encoding/pem\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/big\"\n\t\"math/rand\"\n\t\"net/url\"\n\t\"os\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/dhui/dktest\"\n\t\"github.com/go-sql-driver/mysql\"\n\t\"github.com/golang-migrate/migrate/v4\"\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t\"github.com/golang-migrate/migrate/v4/dktesting\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nconst defaultPort = 3306\n\nvar (\n\topts = dktest.Options{\n\t\tEnv:          map[string]string{\"MYSQL_ROOT_PASSWORD\": \"root\", \"MYSQL_DATABASE\": \"public\"},\n\t\tPortRequired: true, ReadyFunc: isReady,\n\t}\n\toptsAnsiQuotes = dktest.Options{\n\t\tEnv:          map[string]string{\"MYSQL_ROOT_PASSWORD\": \"root\", \"MYSQL_DATABASE\": \"public\"},\n\t\tPortRequired: true, ReadyFunc: isReady,\n\t\tCmd: []string{\"--sql-mode=ANSI_QUOTES\"},\n\t}\n\t// Supported versions: https://www.mysql.com/support/supportedplatforms/database.html\n\tspecs = []dktesting.ContainerSpec{\n\t\t{ImageName: \"mysql:8.0\", Options: opts},\n\t\t{ImageName: \"mysql:8.4\", Options: opts},\n\t\t{ImageName: \"mysql:9.0\", Options: opts},\n\t}\n\tspecsAnsiQuotes = []dktesting.ContainerSpec{\n\t\t{ImageName: \"mysql:8.0\", Options: optsAnsiQuotes},\n\t\t{ImageName: \"mysql:8.4\", Options: optsAnsiQuotes},\n\t\t{ImageName: \"mysql:9.0\", Options: optsAnsiQuotes},\n\t}\n)\n\nfunc isReady(ctx context.Context, c dktest.ContainerInfo) bool {\n\tip, port, err := c.Port(defaultPort)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tdb, err := sql.Open(\"mysql\", fmt.Sprintf(\"root:root@tcp(%v:%v)/public\", ip, port))\n\tif err != nil {\n\t\treturn false\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\tlog.Println(\"close error:\", err)\n\t\t}\n\t}()\n\tif err = db.PingContext(ctx); err != nil {\n\t\tswitch err {\n\t\tcase sqldriver.ErrBadConn, mysql.ErrInvalidConn:\n\t\t\treturn false\n\t\tdefault:\n\t\t\tfmt.Println(err)\n\t\t}\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc Test(t *testing.T) {\n\t// mysql.SetLogger(mysql.Logger(log.New(io.Discard, \"\", log.Ltime)))\n\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fmt.Sprintf(\"mysql://root:root@tcp(%v:%v)/public\", ip, port)\n\t\tp := &Mysql{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\n\t\t// check ensureVersionTable\n\t\tif err := d.(*Mysql).ensureVersionTable(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\t// check again\n\t\tif err := d.(*Mysql).ensureVersionTable(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc TestMigrate(t *testing.T) {\n\t// mysql.SetLogger(mysql.Logger(log.New(io.Discard, \"\", log.Ltime)))\n\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fmt.Sprintf(\"mysql://root:root@tcp(%v:%v)/public\", ip, port)\n\t\tp := &Mysql{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://./examples/migrations\", \"public\", d)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\n\t\t// check ensureVersionTable\n\t\tif err := d.(*Mysql).ensureVersionTable(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\t// check again\n\t\tif err := d.(*Mysql).ensureVersionTable(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc TestMigrateAnsiQuotes(t *testing.T) {\n\t// mysql.SetLogger(mysql.Logger(log.New(io.Discard, \"\", log.Ltime)))\n\n\tdktesting.ParallelTest(t, specsAnsiQuotes, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fmt.Sprintf(\"mysql://root:root@tcp(%v:%v)/public\", ip, port)\n\t\tp := &Mysql{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://./examples/migrations\", \"public\", d)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\n\t\t// check ensureVersionTable\n\t\tif err := d.(*Mysql).ensureVersionTable(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\t// check again\n\t\tif err := d.(*Mysql).ensureVersionTable(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc TestLockWorks(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fmt.Sprintf(\"mysql://root:root@tcp(%v:%v)/public\", ip, port)\n\t\tp := &Mysql{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\n\t\tms := d.(*Mysql)\n\n\t\terr = ms.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\terr = ms.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// make sure the 2nd lock works (RELEASE_LOCK is very finicky)\n\t\terr = ms.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\terr = ms.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc TestNoLockParamValidation(t *testing.T) {\n\tip := \"127.0.0.1\"\n\tport := 3306\n\taddr := fmt.Sprintf(\"mysql://root:root@tcp(%v:%v)/public\", ip, port)\n\tp := &Mysql{}\n\t_, err := p.Open(addr + \"?x-no-lock=not-a-bool\")\n\tif !errors.Is(err, strconv.ErrSyntax) {\n\t\tt.Fatal(\"Expected syntax error when passing a non-bool as x-no-lock parameter\")\n\t}\n}\n\nfunc TestNoLockWorks(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fmt.Sprintf(\"mysql://root:root@tcp(%v:%v)/public\", ip, port)\n\t\tp := &Mysql{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tlock := d.(*Mysql)\n\n\t\tp = &Mysql{}\n\t\td, err = p.Open(addr + \"?x-no-lock=true\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tnoLock := d.(*Mysql)\n\n\t\t// Should be possible to take real lock and no-lock at the same time\n\t\tif err = lock.Lock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err = noLock.Lock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err = lock.Unlock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err = noLock.Unlock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc TestExtractCustomQueryParams(t *testing.T) {\n\ttestcases := []struct {\n\t\tname                 string\n\t\tconfig               *mysql.Config\n\t\texpectedParams       map[string]string\n\t\texpectedCustomParams map[string]string\n\t\texpectedErr          error\n\t}{\n\t\t{name: \"nil config\", expectedErr: ErrNilConfig},\n\t\t{\n\t\t\tname:                 \"no params\",\n\t\t\tconfig:               mysql.NewConfig(),\n\t\t\texpectedCustomParams: map[string]string{},\n\t\t},\n\t\t{\n\t\t\tname:                 \"no custom params\",\n\t\t\tconfig:               &mysql.Config{Params: map[string]string{\"hello\": \"world\"}},\n\t\t\texpectedParams:       map[string]string{\"hello\": \"world\"},\n\t\t\texpectedCustomParams: map[string]string{},\n\t\t},\n\t\t{\n\t\t\tname: \"one param, one custom param\",\n\t\t\tconfig: &mysql.Config{\n\t\t\t\tParams: map[string]string{\"hello\": \"world\", \"x-foo\": \"bar\"},\n\t\t\t},\n\t\t\texpectedParams:       map[string]string{\"hello\": \"world\"},\n\t\t\texpectedCustomParams: map[string]string{\"x-foo\": \"bar\"},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple params, multiple custom params\",\n\t\t\tconfig: &mysql.Config{\n\t\t\t\tParams: map[string]string{\n\t\t\t\t\t\"hello\": \"world\",\n\t\t\t\t\t\"x-foo\": \"bar\",\n\t\t\t\t\t\"dead\":  \"beef\",\n\t\t\t\t\t\"x-cat\": \"hat\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedParams:       map[string]string{\"hello\": \"world\", \"dead\": \"beef\"},\n\t\t\texpectedCustomParams: map[string]string{\"x-foo\": \"bar\", \"x-cat\": \"hat\"},\n\t\t},\n\t}\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tcustomParams, err := extractCustomQueryParams(tc.config)\n\t\t\tif tc.config != nil {\n\t\t\t\tassert.Equal(t, tc.expectedParams, tc.config.Params,\n\t\t\t\t\t\"Expected config params have custom params properly removed\")\n\t\t\t}\n\t\t\tassert.Equal(t, tc.expectedErr, err, \"Expected errors to match\")\n\t\t\tassert.Equal(t, tc.expectedCustomParams, customParams,\n\t\t\t\t\"Expected custom params to be properly extracted\")\n\t\t})\n\t}\n}\n\nfunc createTmpCert(t *testing.T) string {\n\ttmpCertFile, err := os.CreateTemp(\"\", \"migrate_test_cert\")\n\tif err != nil {\n\t\tt.Fatal(\"Failed to create temp cert file:\", err)\n\t}\n\tt.Cleanup(func() {\n\t\tif err := os.Remove(tmpCertFile.Name()); err != nil {\n\t\t\tt.Log(\"Failed to cleanup temp cert file:\", err)\n\t\t}\n\t})\n\n\tr := rand.New(rand.NewSource(0))\n\tpub, priv, err := ed25519.GenerateKey(r)\n\tif err != nil {\n\t\tt.Fatal(\"Failed to generate ed25519 key for temp cert file:\", err)\n\t}\n\ttmpl := x509.Certificate{\n\t\tSerialNumber: big.NewInt(0),\n\t}\n\tderBytes, err := x509.CreateCertificate(r, &tmpl, &tmpl, pub, priv)\n\tif err != nil {\n\t\tt.Fatal(\"Failed to generate temp cert file:\", err)\n\t}\n\tif err := pem.Encode(tmpCertFile, &pem.Block{Type: \"CERTIFICATE\", Bytes: derBytes}); err != nil {\n\t\tt.Fatal(\"Failed to encode \")\n\t}\n\tif err := tmpCertFile.Close(); err != nil {\n\t\tt.Fatal(\"Failed to close temp cert file:\", err)\n\t}\n\treturn tmpCertFile.Name()\n}\n\nfunc TestURLToMySQLConfig(t *testing.T) {\n\ttmpCertFilename := createTmpCert(t)\n\ttmpCertFilenameEscaped := url.PathEscape(tmpCertFilename)\n\n\ttestcases := []struct {\n\t\tname        string\n\t\turlStr      string\n\t\texpectedDSN string // empty string signifies that an error is expected\n\t}{\n\t\t{name: \"no user/password\", urlStr: \"mysql://tcp(127.0.0.1:3306)/myDB?multiStatements=true\",\n\t\t\texpectedDSN: \"tcp(127.0.0.1:3306)/myDB?multiStatements=true\"},\n\t\t{name: \"only user\", urlStr: \"mysql://username@tcp(127.0.0.1:3306)/myDB?multiStatements=true\",\n\t\t\texpectedDSN: \"username@tcp(127.0.0.1:3306)/myDB?multiStatements=true\"},\n\t\t{name: \"only user - with encoded :\",\n\t\t\turlStr:      \"mysql://username%3A@tcp(127.0.0.1:3306)/myDB?multiStatements=true\",\n\t\t\texpectedDSN: \"username:@tcp(127.0.0.1:3306)/myDB?multiStatements=true\"},\n\t\t{name: \"only user - with encoded @\",\n\t\t\turlStr:      \"mysql://username%40@tcp(127.0.0.1:3306)/myDB?multiStatements=true\",\n\t\t\texpectedDSN: \"username@@tcp(127.0.0.1:3306)/myDB?multiStatements=true\"},\n\t\t{name: \"user/password\", urlStr: \"mysql://username:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true\",\n\t\t\texpectedDSN: \"username:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true\"},\n\t\t// Not supported yet: https://github.com/go-sql-driver/mysql/issues/591\n\t\t// {name: \"user/password - user with encoded :\",\n\t\t// \turlStr:      \"mysql://username%3A:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true\",\n\t\t// \texpectedDSN: \"username::password@tcp(127.0.0.1:3306)/myDB?multiStatements=true\"},\n\t\t{name: \"user/password - user with encoded @\",\n\t\t\turlStr:      \"mysql://username%40:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true\",\n\t\t\texpectedDSN: \"username@:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true\"},\n\t\t{name: \"user/password - password with encoded :\",\n\t\t\turlStr:      \"mysql://username:password%3A@tcp(127.0.0.1:3306)/myDB?multiStatements=true\",\n\t\t\texpectedDSN: \"username:password:@tcp(127.0.0.1:3306)/myDB?multiStatements=true\"},\n\t\t{name: \"user/password - password with encoded @\",\n\t\t\turlStr:      \"mysql://username:password%40@tcp(127.0.0.1:3306)/myDB?multiStatements=true\",\n\t\t\texpectedDSN: \"username:password@@tcp(127.0.0.1:3306)/myDB?multiStatements=true\"},\n\t\t{name: \"custom tls\",\n\t\t\turlStr:      \"mysql://username:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true&tls=custom&x-tls-ca=\" + tmpCertFilenameEscaped,\n\t\t\texpectedDSN: \"username:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true&tls=custom&x-tls-ca=\" + tmpCertFilenameEscaped},\n\t}\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tconfig, err := urlToMySQLConfig(tc.urlStr)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"Failed to parse url string:\", tc.urlStr, \"error:\", err)\n\t\t\t}\n\t\t\tdsn := config.FormatDSN()\n\t\t\tif dsn != tc.expectedDSN {\n\t\t\t\tt.Error(\"Got unexpected DSN:\", dsn, \"!=\", tc.expectedDSN)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "database/neo4j/README.md",
    "content": "# neo4j\nThe Neo4j driver (bolt) does not natively support executing multiple statements in a single query. To allow for multiple statements in a single migration, you can use the `x-multi-statement` param. \nThis mode splits the migration text into separately-executed statements by a semi-colon `;`. Thus `x-multi-statement` cannot be used when a statement in the migration contains a string with a semi-colon.\nThe queries **should** run in a single transaction, so partial migrations should not be a concern, but this is untested.\n\n\n`neo4j://user:password@host:port/`\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| `x-multi-statement` | `MultiStatement` | Enable multiple statements to be ran in a single migration (See note above) |\n| `user` | Contained within `AuthConfig` | The user to sign in as |\n| `password` | Contained within `AuthConfig` | The user's password |\n| `host` | | The host to connect to. Values that start with / are for unix domain sockets. (default is localhost) |\n| `port` | | The port to bind to. (default is 7687) |\n|  | `MigrationsLabel` | Name of the migrations node label |\n\n## Supported versions\n\nOnly Neo4j v3.5+ is [supported](https://github.com/neo4j/neo4j-go-driver/issues/64#issuecomment-625133600)\n"
  },
  {
    "path": "database/neo4j/TUTORIAL.md",
    "content": "## Create migrations\nLet's create nodes called `Users`:\n```\nmigrate create -ext cypher -dir db/migrations -seq create_user_nodes\n```\nIf there were no errors, we should have two files available under `db/migrations` folder:\n- 000001_create_user_nodes.down.cypher\n- 000001_create_user_nodes.up.cypher\n\nNote the `cypher` extension that we provided.\n\nIn the `.up.cypher` file let's create the table:\n```\nCREATE (u1:User {name: \"Peter\"})\nCREATE (u2:User {name: \"Paul\"})\nCREATE (u3:User {name: \"Mary\"})\n```\nAnd in the `.down.sql` let's delete it:\n```\nMATCH (u:User) WHERE u.name IN [\"Peter\", \"Paul\", \"Mary\"] DELETE u\n```\nIdeally your migrations should be idempotent. You can read more about idempotency in [getting started](GETTING_STARTED.md#create-migrations)\n\n## Run migrations\n```\nmigrate -database ${NEO4J_URL} -path db/migrations up\n```\nLet's check if the table was created properly by running `bin/cypher-shell -u neo4j -p password`, then `neo4j> MATCH (u:User)`\nThe output you are supposed to see:\n```\n+-----------------------------------------------------------------+\n| u                                                               |\n+-----------------------------------------------------------------+\n| (:User {name: \"Peter\")                                          |\n| (:User {name: \"Paul\")                                           |\n| (:User {name: \"Mary\")                                           |\n+-----------------------------------------------------------------+\n```\nGreat! Now let's check if running reverse migration also works:\n```\nmigrate -database ${NEO4J_URL} -path db/migrations down\n```\nMake sure to check if your database changed as expected in this case as well.\n\n## Database transactions\n\nTo show database transactions usage, let's create another set of migrations by running:\n```\nmigrate create -ext cypher -dir db/migrations -seq add_mood_to_users\n```\nAgain, it should create for us two migrations files:\n- 000002_add_mood_to_users.down.cypher\n- 000002_add_mood_to_users.up.cypher\n\nIn Neo4j, when we want our queries to be done in a transaction, we need to wrap it with `:BEGIN` and `:COMMIT` commands.\nMigration up:\n```\n:BEGIN\n\nMATCH (u:User)\nSET u.mood = \"Cheery\"\n\n:COMMIT\n```\nMigration down:\n```\n:BEGIN\n\nMATCH (u:User)\nSET u.mood = null\n\n:COMMIT\n```\n\n## Optional: Run migrations within your Go app\nHere is a very simple app running migrations for the above configuration:\n```\nimport (\n\t\"log\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t_ \"github.com/golang-migrate/migrate/v4/database/neo4j\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nfunc main() {\n\tm, err := migrate.New(\n\t\t\"file://db/migrations\",\n\t\t\"neo4j://neo4j:password@localhost:7687/\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif err := m.Up(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n```"
  },
  {
    "path": "database/neo4j/examples/migrations/1578421040_create_movies_constraint.down.cypher",
    "content": "DROP CONSTRAINT ON (m:Movie) ASSERT m.Name IS UNIQUE"
  },
  {
    "path": "database/neo4j/examples/migrations/1578421040_create_movies_constraint.up.cypher",
    "content": "CREATE CONSTRAINT ON (m:Movie) ASSERT m.Name IS UNIQUE"
  },
  {
    "path": "database/neo4j/examples/migrations/1578421725_create_movies.down.cypher",
    "content": "MATCH (m:Movie)\nDELETE m"
  },
  {
    "path": "database/neo4j/examples/migrations/1578421725_create_movies.up.cypher",
    "content": "CREATE (:Movie {name: \"Footloose\"})\nCREATE (:Movie {name: \"Ghost\"})"
  },
  {
    "path": "database/neo4j/examples/migrations/1578421726_multistatement_test.up.cypher",
    "content": "CREATE (:Movie {name: \"Hollow Man\"});\nCREATE (:Movie {name: \"Mystic River\"});\n;;;"
  },
  {
    "path": "database/neo4j/neo4j.go",
    "content": "package neo4j\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tneturl \"net/url\"\n\t\"strconv\"\n\t\"sync/atomic\"\n\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t\"github.com/golang-migrate/migrate/v4/database/multistmt\"\n\t\"github.com/neo4j/neo4j-go-driver/neo4j\"\n)\n\nfunc init() {\n\tdb := Neo4j{}\n\tdatabase.Register(\"neo4j\", &db)\n}\n\nconst DefaultMigrationsLabel = \"SchemaMigration\"\n\nvar (\n\tStatementSeparator           = []byte(\";\")\n\tDefaultMultiStatementMaxSize = 10 * 1 << 20 // 10 MB\n)\n\nvar (\n\tErrNilConfig = fmt.Errorf(\"no config\")\n)\n\ntype Config struct {\n\tMigrationsLabel       string\n\tMultiStatement        bool\n\tMultiStatementMaxSize int\n}\n\ntype Neo4j struct {\n\tdriver neo4j.Driver\n\tlock   uint32\n\n\t// Open and WithInstance need to guarantee that config is never nil\n\tconfig *Config\n}\n\nfunc WithInstance(driver neo4j.Driver, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tnDriver := &Neo4j{\n\t\tdriver: driver,\n\t\tconfig: config,\n\t}\n\n\tif err := nDriver.ensureVersionConstraint(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn nDriver, nil\n}\n\nfunc (n *Neo4j) Open(url string) (database.Driver, error) {\n\turi, err := neturl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpassword, _ := uri.User.Password()\n\tauthToken := neo4j.BasicAuth(uri.User.Username(), password, \"\")\n\turi.User = nil\n\turi.Scheme = \"bolt\"\n\tmsQuery := uri.Query().Get(\"x-multi-statement\")\n\n\t// Whether to turn on/off TLS encryption.\n\ttlsEncrypted := uri.Query().Get(\"x-tls-encrypted\")\n\tmulti := false\n\tencrypted := false\n\tif msQuery != \"\" {\n\t\tmulti, err = strconv.ParseBool(uri.Query().Get(\"x-multi-statement\"))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif tlsEncrypted != \"\" {\n\t\tencrypted, err = strconv.ParseBool(tlsEncrypted)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tmultiStatementMaxSize := DefaultMultiStatementMaxSize\n\tif s := uri.Query().Get(\"x-multi-statement-max-size\"); s != \"\" {\n\t\tmultiStatementMaxSize, err = strconv.Atoi(s)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\turi.RawQuery = \"\"\n\n\tdriver, err := neo4j.NewDriver(uri.String(), authToken, func(config *neo4j.Config) {\n\t\tconfig.Encrypted = encrypted\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn WithInstance(driver, &Config{\n\t\tMigrationsLabel:       DefaultMigrationsLabel,\n\t\tMultiStatement:        multi,\n\t\tMultiStatementMaxSize: multiStatementMaxSize,\n\t})\n}\n\nfunc (n *Neo4j) Close() error {\n\treturn n.driver.Close()\n}\n\n// local locking in order to pass tests, Neo doesn't support database locking\nfunc (n *Neo4j) Lock() error {\n\tif !atomic.CompareAndSwapUint32(&n.lock, 0, 1) {\n\t\treturn database.ErrLocked\n\t}\n\n\treturn nil\n}\n\nfunc (n *Neo4j) Unlock() error {\n\tif !atomic.CompareAndSwapUint32(&n.lock, 1, 0) {\n\t\treturn database.ErrNotLocked\n\t}\n\treturn nil\n}\n\nfunc (n *Neo4j) Run(migration io.Reader) (err error) {\n\tsession, err := n.driver.Session(neo4j.AccessModeWrite)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif cerr := session.Close(); cerr != nil {\n\t\t\terr = errors.Join(err, cerr)\n\t\t}\n\t}()\n\n\tif n.config.MultiStatement {\n\t\t_, err = session.WriteTransaction(func(transaction neo4j.Transaction) (interface{}, error) {\n\t\t\tvar stmtRunErr error\n\t\t\tif err := multistmt.Parse(migration, StatementSeparator, n.config.MultiStatementMaxSize, func(stmt []byte) bool {\n\t\t\t\ttrimStmt := bytes.TrimSpace(stmt)\n\t\t\t\tif len(trimStmt) == 0 {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\ttrimStmt = bytes.TrimSuffix(trimStmt, StatementSeparator)\n\t\t\t\tif len(trimStmt) == 0 {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\n\t\t\t\tresult, err := transaction.Run(string(trimStmt), nil)\n\t\t\t\tif _, err := neo4j.Collect(result, err); err != nil {\n\t\t\t\t\tstmtRunErr = err\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t}); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn nil, stmtRunErr\n\t\t})\n\t\treturn err\n\t}\n\n\tbody, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = neo4j.Collect(session.Run(string(body[:]), nil))\n\treturn err\n}\n\nfunc (n *Neo4j) SetVersion(version int, dirty bool) (err error) {\n\tsession, err := n.driver.Session(neo4j.AccessModeWrite)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif cerr := session.Close(); cerr != nil {\n\t\t\terr = errors.Join(err, cerr)\n\t\t}\n\t}()\n\n\tquery := fmt.Sprintf(\"MERGE (sm:%s {version: $version}) SET sm.dirty = $dirty, sm.ts = datetime()\",\n\t\tn.config.MigrationsLabel)\n\t_, err = neo4j.Collect(session.Run(query, map[string]interface{}{\"version\": version, \"dirty\": dirty}))\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\ntype MigrationRecord struct {\n\tVersion int\n\tDirty   bool\n}\n\nfunc (n *Neo4j) Version() (version int, dirty bool, err error) {\n\tsession, err := n.driver.Session(neo4j.AccessModeRead)\n\tif err != nil {\n\t\treturn database.NilVersion, false, err\n\t}\n\tdefer func() {\n\t\tif cerr := session.Close(); cerr != nil {\n\t\t\terr = errors.Join(err, cerr)\n\t\t}\n\t}()\n\n\tquery := fmt.Sprintf(`MATCH (sm:%s) RETURN sm.version AS version, sm.dirty AS dirty\nORDER BY COALESCE(sm.ts, datetime({year: 0})) DESC, sm.version DESC LIMIT 1`,\n\t\tn.config.MigrationsLabel)\n\tresult, err := session.ReadTransaction(func(transaction neo4j.Transaction) (interface{}, error) {\n\t\tresult, err := transaction.Run(query, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif result.Next() {\n\t\t\trecord := result.Record()\n\t\t\tmr := MigrationRecord{}\n\t\t\tversionResult, ok := record.Get(\"version\")\n\t\t\tif !ok {\n\t\t\t\tmr.Version = database.NilVersion\n\t\t\t} else {\n\t\t\t\tmr.Version = int(versionResult.(int64))\n\t\t\t}\n\n\t\t\tdirtyResult, ok := record.Get(\"dirty\")\n\t\t\tif ok {\n\t\t\t\tmr.Dirty = dirtyResult.(bool)\n\t\t\t}\n\n\t\t\treturn mr, nil\n\t\t}\n\t\treturn nil, result.Err()\n\t})\n\tif err != nil {\n\t\treturn database.NilVersion, false, err\n\t}\n\tif result == nil {\n\t\treturn database.NilVersion, false, err\n\t}\n\tmr := result.(MigrationRecord)\n\treturn mr.Version, mr.Dirty, err\n}\n\nfunc (n *Neo4j) Drop() (err error) {\n\tsession, err := n.driver.Session(neo4j.AccessModeWrite)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif cerr := session.Close(); cerr != nil {\n\t\t\terr = errors.Join(err, cerr)\n\t\t}\n\t}()\n\n\tif _, err := neo4j.Collect(session.Run(\"MATCH (n) DETACH DELETE n\", nil)); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (n *Neo4j) ensureVersionConstraint() (err error) {\n\tsession, err := n.driver.Session(neo4j.AccessModeWrite)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif cerr := session.Close(); cerr != nil {\n\t\t\terr = errors.Join(err, cerr)\n\t\t}\n\t}()\n\n\t/**\n\tGet constraint and check to avoid error duplicate\n\tusing db.labels() to support Neo4j 3 and 4.\n\tNeo4J 3 doesn't support db.constraints() YIELD name\n\t*/\n\tres, err := neo4j.Collect(session.Run(fmt.Sprintf(\"CALL db.labels() YIELD label WHERE label=\\\"%s\\\" RETURN label\", n.config.MigrationsLabel), nil))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(res) == 1 {\n\t\treturn nil\n\t}\n\n\tquery := fmt.Sprintf(\"CREATE CONSTRAINT ON (a:%s) ASSERT a.version IS UNIQUE\", n.config.MigrationsLabel)\n\tif _, err := neo4j.Collect(session.Run(query, nil)); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "database/neo4j/neo4j_test.go",
    "content": "package neo4j\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"testing\"\n\n\t\"github.com/dhui/dktest\"\n\t\"github.com/neo4j/neo4j-go-driver/neo4j\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t\"github.com/golang-migrate/migrate/v4/dktesting\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nvar (\n\topts = dktest.Options{PortRequired: true, ReadyFunc: isReady,\n\t\tEnv: map[string]string{\"NEO4J_AUTH\": \"neo4j/migratetest\", \"NEO4J_ACCEPT_LICENSE_AGREEMENT\": \"yes\"}}\n\tspecs = []dktesting.ContainerSpec{\n\t\t{ImageName: \"neo4j:4.0\", Options: opts},\n\t\t{ImageName: \"neo4j:4.0-enterprise\", Options: opts},\n\t\t{ImageName: \"neo4j:3.5\", Options: opts},\n\t\t{ImageName: \"neo4j:3.5-enterprise\", Options: opts},\n\t}\n)\n\nfunc neoConnectionString(host, port string) string {\n\treturn fmt.Sprintf(\"bolt://neo4j:migratetest@%s:%s\", host, port)\n}\n\nfunc isReady(ctx context.Context, c dktest.ContainerInfo) bool {\n\tip, port, err := c.Port(7687)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tdriver, err := neo4j.NewDriver(\n\t\tneoConnectionString(ip, port),\n\t\tneo4j.BasicAuth(\"neo4j\", \"migratetest\", \"\"),\n\t\tfunc(config *neo4j.Config) {\n\t\t\tconfig.Encrypted = false\n\t\t})\n\tif err != nil {\n\t\treturn false\n\t}\n\tdefer func() {\n\t\tif err := driver.Close(); err != nil {\n\t\t\tlog.Println(\"close error:\", err)\n\t\t}\n\t}()\n\tsession, err := driver.Session(neo4j.AccessModeRead)\n\tif err != nil {\n\t\treturn false\n\t}\n\tresult, err := session.Run(\"RETURN 1\", nil)\n\tif err != nil {\n\t\treturn false\n\t} else if result.Err() != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc Test(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(7687)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tn := &Neo4j{}\n\t\td, err := n.Open(neoConnectionString(ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tdt.Test(t, d, []byte(\"MATCH (a) RETURN a\"))\n\t})\n}\n\nfunc TestMigrate(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(7687)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tn := &Neo4j{}\n\t\tneoUrl := neoConnectionString(ip, port) + \"/?x-multi-statement=true\"\n\t\td, err := n.Open(neoUrl)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://./examples/migrations\", \"neo4j\", d)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\t})\n}\n\nfunc TestMalformed(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(7687)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tn := &Neo4j{}\n\t\td, err := n.Open(neoConnectionString(ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tmigration := bytes.NewReader([]byte(\"CREATE (a {qid: 1) RETURN a\"))\n\t\tif err := d.Run(migration); err == nil {\n\t\t\tt.Fatal(\"expected failure for malformed migration\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "database/parse_test.go",
    "content": "package database_test\n\nimport (\n\t\"encoding/hex\"\n\t\"net/url\"\n\t\"strings\"\n\t\"testing\"\n)\n\nconst reservedChars = \"!#$%&'()*+,/:;=?@[]\"\nconst reservedCharTestNamePrefix = \"reserved char \"\n\nconst baseUsername = \"username\"\n\nconst scheme = \"database://\"\n\n// TestUserUnencodedReservedURLChars documents the behavior of using unencoded reserved characters in usernames with\n// net/url Parse()\nfunc TestUserUnencodedReservedURLChars(t *testing.T) {\n\turlSuffix := \"password@localhost:12345/myDB?someParam=true\"\n\turlSuffixAndSep := \":\" + urlSuffix\n\n\ttestcases := []struct {\n\t\tchar             string\n\t\tparses           bool\n\t\texpectedUsername string // empty string means that the username failed to parse\n\t\tencodedURL       string\n\t}{\n\t\t{char: \"!\", parses: true, expectedUsername: baseUsername + \"!\",\n\t\t\tencodedURL: scheme + baseUsername + \"%21\" + urlSuffixAndSep},\n\t\t{char: \"#\", parses: true, expectedUsername: \"\",\n\t\t\tencodedURL: scheme + baseUsername + \"#\" + urlSuffixAndSep},\n\t\t{char: \"$\", parses: true, expectedUsername: baseUsername + \"$\",\n\t\t\tencodedURL: scheme + baseUsername + \"$\" + urlSuffixAndSep},\n\t\t{char: \"%\", parses: false},\n\t\t{char: \"&\", parses: true, expectedUsername: baseUsername + \"&\",\n\t\t\tencodedURL: scheme + baseUsername + \"&\" + urlSuffixAndSep},\n\t\t{char: \"'\", parses: true, expectedUsername: \"username'\",\n\t\t\tencodedURL: scheme + baseUsername + \"%27\" + urlSuffixAndSep},\n\t\t{char: \"(\", parses: true, expectedUsername: \"username(\",\n\t\t\tencodedURL: scheme + baseUsername + \"%28\" + urlSuffixAndSep},\n\t\t{char: \")\", parses: true, expectedUsername: \"username)\",\n\t\t\tencodedURL: scheme + baseUsername + \"%29\" + urlSuffixAndSep},\n\t\t{char: \"*\", parses: true, expectedUsername: \"username*\",\n\t\t\tencodedURL: scheme + baseUsername + \"%2A\" + urlSuffixAndSep},\n\t\t{char: \"+\", parses: true, expectedUsername: \"username+\",\n\t\t\tencodedURL: scheme + baseUsername + \"+\" + urlSuffixAndSep},\n\t\t{char: \",\", parses: true, expectedUsername: \"username,\",\n\t\t\tencodedURL: scheme + baseUsername + \",\" + urlSuffixAndSep},\n\t\t{char: \"/\", parses: true, expectedUsername: \"\",\n\t\t\tencodedURL: scheme + baseUsername + \"/\" + urlSuffixAndSep},\n\t\t{char: \":\", parses: true, expectedUsername: baseUsername,\n\t\t\tencodedURL: scheme + baseUsername + \":%3A\" + urlSuffix},\n\t\t{char: \";\", parses: true, expectedUsername: \"username;\",\n\t\t\tencodedURL: scheme + baseUsername + \";\" + urlSuffixAndSep},\n\t\t{char: \"=\", parses: true, expectedUsername: \"username=\",\n\t\t\tencodedURL: scheme + baseUsername + \"=\" + urlSuffixAndSep},\n\t\t{char: \"?\", parses: true, expectedUsername: \"\",\n\t\t\tencodedURL: scheme + baseUsername + \"?\" + urlSuffixAndSep},\n\t\t{char: \"@\", parses: true, expectedUsername: \"username@\",\n\t\t\tencodedURL: scheme + baseUsername + \"%40\" + urlSuffixAndSep},\n\t\t{char: \"[\", parses: false},\n\t\t{char: \"]\", parses: false},\n\t}\n\n\ttestedChars := make([]string, 0, len(reservedChars))\n\tfor _, tc := range testcases {\n\t\ttestedChars = append(testedChars, tc.char)\n\t\tt.Run(reservedCharTestNamePrefix+tc.char, func(t *testing.T) {\n\t\t\ts := scheme + baseUsername + tc.char + urlSuffixAndSep\n\t\t\tu, err := url.Parse(s)\n\t\t\tif err == nil {\n\t\t\t\tif !tc.parses {\n\t\t\t\t\tt.Error(\"Unexpectedly parsed reserved character. url:\", s)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tvar username string\n\t\t\t\tif u.User != nil {\n\t\t\t\t\tusername = u.User.Username()\n\t\t\t\t}\n\t\t\t\tif username != tc.expectedUsername {\n\t\t\t\t\tt.Error(\"Got unexpected username:\", username, \"!=\", tc.expectedUsername)\n\t\t\t\t}\n\t\t\t\tif s := u.String(); s != tc.encodedURL {\n\t\t\t\t\tt.Error(\"Got unexpected encoded URL:\", s, \"!=\", tc.encodedURL)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif tc.parses {\n\t\t\t\t\tt.Error(\"Failed to parse reserved character. url:\", s)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\tt.Run(\"All reserved chars tested\", func(t *testing.T) {\n\t\tif s := strings.Join(testedChars, \"\"); s != reservedChars {\n\t\t\tt.Error(\"Not all reserved URL characters were tested:\", s, \"!=\", reservedChars)\n\t\t}\n\t})\n}\n\nfunc TestUserEncodedReservedURLChars(t *testing.T) {\n\turlSuffix := \"password@localhost:12345/myDB?someParam=true\"\n\turlSuffixAndSep := \":\" + urlSuffix\n\n\tfor _, c := range reservedChars {\n\t\tc := string(c)\n\t\tt.Run(reservedCharTestNamePrefix+c, func(t *testing.T) {\n\t\t\tencodedChar := \"%\" + hex.EncodeToString([]byte(c))\n\t\t\ts := scheme + baseUsername + encodedChar + urlSuffixAndSep\n\t\t\texpectedUsername := baseUsername + c\n\t\t\tu, err := url.Parse(s)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"Failed to parse url with encoded reserved character. url:\", s)\n\t\t\t}\n\t\t\tif u.User == nil {\n\t\t\t\tt.Fatal(\"Failed to parse userinfo with encoded reserve character. url:\", s)\n\t\t\t}\n\t\t\tif username := u.User.Username(); username != expectedUsername {\n\t\t\t\tt.Fatal(\"Got unexpected username:\", username, \"!=\", expectedUsername)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestPasswordUnencodedReservedURLChars documents the behavior of using unencoded reserved characters in passwords\n// with net/url Parse()\nfunc TestPasswordUnencodedReservedURLChars(t *testing.T) {\n\tusername := baseUsername\n\tschemeAndUsernameAndSep := scheme + username + \":\"\n\tbasePassword := \"password\"\n\turlSuffixAndSep := \"@localhost:12345/myDB?someParam=true\"\n\n\ttestcases := []struct {\n\t\tchar             string\n\t\tparses           bool\n\t\texpectedUsername string // empty string means that the username failed to parse\n\t\texpectedPassword string // empty string means that the password failed to parse\n\t\tencodedURL       string\n\t}{\n\t\t{char: \"!\", parses: true, expectedUsername: username, expectedPassword: basePassword + \"!\",\n\t\t\tencodedURL: schemeAndUsernameAndSep + basePassword + \"%21\" + urlSuffixAndSep},\n\t\t{char: \"#\", parses: false},\n\t\t{char: \"$\", parses: true, expectedUsername: username, expectedPassword: basePassword + \"$\",\n\t\t\tencodedURL: schemeAndUsernameAndSep + basePassword + \"$\" + urlSuffixAndSep},\n\t\t{char: \"%\", parses: false},\n\t\t{char: \"&\", parses: true, expectedUsername: username, expectedPassword: basePassword + \"&\",\n\t\t\tencodedURL: schemeAndUsernameAndSep + basePassword + \"&\" + urlSuffixAndSep},\n\t\t{char: \"'\", parses: true, expectedUsername: username, expectedPassword: \"password'\",\n\t\t\tencodedURL: schemeAndUsernameAndSep + basePassword + \"%27\" + urlSuffixAndSep},\n\t\t{char: \"(\", parses: true, expectedUsername: username, expectedPassword: \"password(\",\n\t\t\tencodedURL: schemeAndUsernameAndSep + basePassword + \"%28\" + urlSuffixAndSep},\n\t\t{char: \")\", parses: true, expectedUsername: username, expectedPassword: \"password)\",\n\t\t\tencodedURL: schemeAndUsernameAndSep + basePassword + \"%29\" + urlSuffixAndSep},\n\t\t{char: \"*\", parses: true, expectedUsername: username, expectedPassword: \"password*\",\n\t\t\tencodedURL: schemeAndUsernameAndSep + basePassword + \"%2A\" + urlSuffixAndSep},\n\t\t{char: \"+\", parses: true, expectedUsername: username, expectedPassword: \"password+\",\n\t\t\tencodedURL: schemeAndUsernameAndSep + basePassword + \"+\" + urlSuffixAndSep},\n\t\t{char: \",\", parses: true, expectedUsername: username, expectedPassword: \"password,\",\n\t\t\tencodedURL: schemeAndUsernameAndSep + basePassword + \",\" + urlSuffixAndSep},\n\t\t{char: \"/\", parses: false},\n\t\t{char: \":\", parses: true, expectedUsername: username, expectedPassword: \"password:\",\n\t\t\tencodedURL: schemeAndUsernameAndSep + basePassword + \"%3A\" + urlSuffixAndSep},\n\t\t{char: \";\", parses: true, expectedUsername: username, expectedPassword: \"password;\",\n\t\t\tencodedURL: schemeAndUsernameAndSep + basePassword + \";\" + urlSuffixAndSep},\n\t\t{char: \"=\", parses: true, expectedUsername: username, expectedPassword: \"password=\",\n\t\t\tencodedURL: schemeAndUsernameAndSep + basePassword + \"=\" + urlSuffixAndSep},\n\t\t{char: \"?\", parses: false},\n\t\t{char: \"@\", parses: true, expectedUsername: username, expectedPassword: \"password@\",\n\t\t\tencodedURL: schemeAndUsernameAndSep + basePassword + \"%40\" + urlSuffixAndSep},\n\t\t{char: \"[\", parses: false},\n\t\t{char: \"]\", parses: false},\n\t}\n\n\ttestedChars := make([]string, 0, len(reservedChars))\n\tfor _, tc := range testcases {\n\t\ttestedChars = append(testedChars, tc.char)\n\t\tt.Run(reservedCharTestNamePrefix+tc.char, func(t *testing.T) {\n\t\t\ts := schemeAndUsernameAndSep + basePassword + tc.char + urlSuffixAndSep\n\t\t\tu, err := url.Parse(s)\n\t\t\tif err == nil {\n\t\t\t\tif !tc.parses {\n\t\t\t\t\tt.Error(\"Unexpectedly parsed reserved character. url:\", s)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tvar username, password string\n\t\t\t\tif u.User != nil {\n\t\t\t\t\tusername = u.User.Username()\n\t\t\t\t\tpassword, _ = u.User.Password()\n\t\t\t\t}\n\t\t\t\tif username != tc.expectedUsername {\n\t\t\t\t\tt.Error(\"Got unexpected username:\", username, \"!=\", tc.expectedUsername)\n\t\t\t\t}\n\t\t\t\tif password != tc.expectedPassword {\n\t\t\t\t\tt.Error(\"Got unexpected password:\", password, \"!=\", tc.expectedPassword)\n\t\t\t\t}\n\t\t\t\tif s := u.String(); s != tc.encodedURL {\n\t\t\t\t\tt.Error(\"Got unexpected encoded URL:\", s, \"!=\", tc.encodedURL)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif tc.parses {\n\t\t\t\t\tt.Error(\"Failed to parse reserved character. url:\", s)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\tt.Run(\"All reserved chars tested\", func(t *testing.T) {\n\t\tif s := strings.Join(testedChars, \"\"); s != reservedChars {\n\t\t\tt.Error(\"Not all reserved URL characters were tested:\", s, \"!=\", reservedChars)\n\t\t}\n\t})\n}\n\nfunc TestPasswordEncodedReservedURLChars(t *testing.T) {\n\tusername := baseUsername\n\tschemeAndUsernameAndSep := scheme + username + \":\"\n\tbasePassword := \"password\"\n\turlSuffixAndSep := \"@localhost:12345/myDB?someParam=true\"\n\n\tfor _, c := range reservedChars {\n\t\tc := string(c)\n\t\tt.Run(reservedCharTestNamePrefix+c, func(t *testing.T) {\n\t\t\tencodedChar := \"%\" + hex.EncodeToString([]byte(c))\n\t\t\ts := schemeAndUsernameAndSep + basePassword + encodedChar + urlSuffixAndSep\n\t\t\texpectedPassword := basePassword + c\n\t\t\tu, err := url.Parse(s)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"Failed to parse url with encoded reserved character. url:\", s)\n\t\t\t}\n\t\t\tif u.User == nil {\n\t\t\t\tt.Fatal(\"Failed to parse userinfo with encoded reserve character. url:\", s)\n\t\t\t}\n\t\t\tif n := u.User.Username(); n != username {\n\t\t\t\tt.Fatal(\"Got unexpected username:\", n, \"!=\", username)\n\t\t\t}\n\t\t\tif p, _ := u.User.Password(); p != expectedPassword {\n\t\t\t\tt.Fatal(\"Got unexpected password:\", p, \"!=\", expectedPassword)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "database/pgx/README.md",
    "content": "# pgx\n\nThis package is for [pgx/v4](https://pkg.go.dev/github.com/jackc/pgx/v4). A backend for the newer [pgx/v5](https://pkg.go.dev/github.com/jackc/pgx/v5) is [also available](v5).\n\n`pgx://user:password@host:port/dbname?query`\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |\n| `x-migrations-table-quoted` | `MigrationsTableQuoted` | By default, migrate quotes the migration table for SQL injection safety reasons. This option disable quoting and naively checks that you have quoted the migration table name. e.g. `\"my_schema\".\"schema_migrations\"` |\n| `x-statement-timeout` | `StatementTimeout` | Abort any statement that takes more than the specified number of milliseconds |\n| `x-multi-statement` | `MultiStatementEnabled` | Enable multi-statement execution (default: false) |\n| `x-multi-statement-max-size` | `MultiStatementMaxSize` | Maximum size of single statement in bytes (default: 10MB) |\n| `x-lock-strategy` | `LockStrategy` | Strategy used for locking during migration (default: advisory) |\n| `x-lock-table` | `LockTable` | Name of the table which maintains the migration lock (default: schema_lock) |\n| `dbname` | `DatabaseName` | The name of the database to connect to |\n| `search_path` | | This variable specifies the order in which schemas are searched when an object is referenced by a simple name with no schema specified. |\n| `user` | | The user to sign in as |\n| `password` | | The user's password |\n| `host` | | The host to connect to. Values that start with / are for unix domain sockets. (default is localhost) |\n| `port` | | The port to bind to. (default is 5432) |\n| `fallback_application_name` | | An application_name to fall back to if one isn't provided. |\n| `connect_timeout` | | Maximum wait for connection, in seconds. Zero or not specified means wait indefinitely. |\n| `sslcert` | | Cert file location. The file must contain PEM encoded data. |\n| `sslkey` | | Key file location. The file must contain PEM encoded data. |\n| `sslrootcert` | | The location of the root certificate file. The file must contain PEM encoded data. |\n| `sslmode` | | Whether or not to use SSL (disable\\|require\\|verify-ca\\|verify-full) |\n\n\n## Upgrading from v1\n\n1. Write down the current migration version from schema_migrations\n1. `DROP TABLE schema_migrations`\n2. Wrap your existing migrations in transactions ([BEGIN/COMMIT](https://www.postgresql.org/docs/current/static/transaction-iso.html)) if you use multiple statements within one migration.\n3. Download and install the latest migrate version.\n4. Force the current migration version with `migrate force <current_version>`.\n\n## Multi-statement mode\n\nIn PostgreSQL running multiple SQL statements in one `Exec` executes them inside a transaction. Sometimes this\nbehavior is not desirable because some statements can be only run outside of transaction (e.g.\n`CREATE INDEX CONCURRENTLY`). If you want to use `CREATE INDEX CONCURRENTLY` without activating multi-statement mode\nyou have to put such statements in a separate migration files.\n"
  },
  {
    "path": "database/pgx/examples/migrations/1085649617_create_users_table.down.sql",
    "content": "DROP TABLE IF EXISTS users;\n"
  },
  {
    "path": "database/pgx/examples/migrations/1085649617_create_users_table.up.sql",
    "content": "CREATE TABLE users (\n  user_id integer unique,\n  name    varchar(40),\n  email   varchar(40)\n);\n"
  },
  {
    "path": "database/pgx/examples/migrations/1185749658_add_city_to_users.down.sql",
    "content": "ALTER TABLE users DROP COLUMN IF EXISTS city;\n"
  },
  {
    "path": "database/pgx/examples/migrations/1185749658_add_city_to_users.up.sql",
    "content": "ALTER TABLE users ADD COLUMN city varchar(100);\n\n\n"
  },
  {
    "path": "database/pgx/examples/migrations/1285849751_add_index_on_user_emails.down.sql",
    "content": "DROP INDEX IF EXISTS users_email_index;\n"
  },
  {
    "path": "database/pgx/examples/migrations/1285849751_add_index_on_user_emails.up.sql",
    "content": "CREATE UNIQUE INDEX CONCURRENTLY users_email_index ON users (email);\n\n-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/pgx/examples/migrations/1385949617_create_books_table.down.sql",
    "content": "DROP TABLE IF EXISTS books;\n"
  },
  {
    "path": "database/pgx/examples/migrations/1385949617_create_books_table.up.sql",
    "content": "CREATE TABLE books (\n  user_id integer,\n  name    varchar(40),\n  author  varchar(40)\n);\n"
  },
  {
    "path": "database/pgx/examples/migrations/1485949617_create_movies_table.down.sql",
    "content": "DROP TABLE IF EXISTS movies;\n"
  },
  {
    "path": "database/pgx/examples/migrations/1485949617_create_movies_table.up.sql",
    "content": "CREATE TABLE movies (\n  user_id   integer,\n  name      varchar(40),\n  director  varchar(40)\n);\n"
  },
  {
    "path": "database/pgx/examples/migrations/1585849751_just_a_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/pgx/examples/migrations/1685849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/pgx/examples/migrations/1785849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/pgx/examples/migrations/1885849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/pgx/pgx.go",
    "content": "//go:build go1.9\n\npackage pgx\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t\"github.com/golang-migrate/migrate/v4/database/multistmt\"\n\t\"github.com/jackc/pgconn\"\n\t\"github.com/jackc/pgerrcode\"\n\t_ \"github.com/jackc/pgx/v4/stdlib\"\n\t\"github.com/lib/pq\"\n)\n\nconst (\n\tLockStrategyAdvisory = \"advisory\"\n\tLockStrategyTable    = \"table\"\n)\n\nfunc init() {\n\tdb := Postgres{}\n\tdatabase.Register(\"pgx\", &db)\n\tdatabase.Register(\"pgx4\", &db)\n}\n\nvar (\n\tmultiStmtDelimiter = []byte(\";\")\n\n\tDefaultMigrationsTable       = \"schema_migrations\"\n\tDefaultMultiStatementMaxSize = 10 * 1 << 20 // 10 MB\n\tDefaultLockTable             = \"schema_lock\"\n\tDefaultLockStrategy          = LockStrategyAdvisory\n)\n\nvar (\n\tErrNilConfig      = fmt.Errorf(\"no config\")\n\tErrNoDatabaseName = fmt.Errorf(\"no database name\")\n\tErrNoSchema       = fmt.Errorf(\"no schema\")\n\tErrDatabaseDirty  = fmt.Errorf(\"database is dirty\")\n)\n\ntype Config struct {\n\tMigrationsTable       string\n\tDatabaseName          string\n\tSchemaName            string\n\tLockTable             string\n\tLockStrategy          string\n\tmigrationsSchemaName  string\n\tmigrationsTableName   string\n\tStatementTimeout      time.Duration\n\tMigrationsTableQuoted bool\n\tMultiStatementEnabled bool\n\tMultiStatementMaxSize int\n}\n\ntype Postgres struct {\n\t// Locking and unlocking need to use the same connection\n\tconn     *sql.Conn\n\tdb       *sql.DB\n\tisLocked atomic.Bool\n\n\t// Open and WithInstance need to guarantee that config is never nil\n\tconfig *Config\n}\n\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif err := instance.Ping(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif config.DatabaseName == \"\" {\n\t\tquery := `SELECT CURRENT_DATABASE()`\n\t\tvar databaseName string\n\t\tif err := instance.QueryRow(query).Scan(&databaseName); err != nil {\n\t\t\treturn nil, &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\tif len(databaseName) == 0 {\n\t\t\treturn nil, ErrNoDatabaseName\n\t\t}\n\n\t\tconfig.DatabaseName = databaseName\n\t}\n\n\tif config.SchemaName == \"\" {\n\t\tquery := `SELECT CURRENT_SCHEMA()`\n\t\tvar schemaName string\n\t\tif err := instance.QueryRow(query).Scan(&schemaName); err != nil {\n\t\t\treturn nil, &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\tif len(schemaName) == 0 {\n\t\t\treturn nil, ErrNoSchema\n\t\t}\n\n\t\tconfig.SchemaName = schemaName\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tif len(config.LockTable) == 0 {\n\t\tconfig.LockTable = DefaultLockTable\n\t}\n\n\tif len(config.LockStrategy) == 0 {\n\t\tconfig.LockStrategy = DefaultLockStrategy\n\t}\n\n\tconfig.migrationsSchemaName = config.SchemaName\n\tconfig.migrationsTableName = config.MigrationsTable\n\tif config.MigrationsTableQuoted {\n\t\tre := regexp.MustCompile(`\"(.*?)\"`)\n\t\tresult := re.FindAllStringSubmatch(config.MigrationsTable, -1)\n\t\tconfig.migrationsTableName = result[len(result)-1][1]\n\t\tif len(result) == 2 {\n\t\t\tconfig.migrationsSchemaName = result[0][1]\n\t\t} else if len(result) > 2 {\n\t\t\treturn nil, fmt.Errorf(\"\\\"%s\\\" MigrationsTable contains too many dot characters\", config.MigrationsTable)\n\t\t}\n\t}\n\n\tconn, err := instance.Conn(context.Background())\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpx := &Postgres{\n\t\tconn:   conn,\n\t\tdb:     instance,\n\t\tconfig: config,\n\t}\n\n\tif err := px.ensureLockTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := px.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\nfunc (p *Postgres) Open(url string) (database.Driver, error) {\n\tpurl, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Driver is registered as pgx, but connection string must use postgres schema\n\t// when making actual connection\n\t// i.e. pgx://user:password@host:port/db => postgres://user:password@host:port/db\n\tpurl.Scheme = \"postgres\"\n\n\tdb, err := sql.Open(\"pgx/v4\", migrate.FilterCustomQuery(purl).String())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmigrationsTable := purl.Query().Get(\"x-migrations-table\")\n\tmigrationsTableQuoted := false\n\tif s := purl.Query().Get(\"x-migrations-table-quoted\"); len(s) > 0 {\n\t\tmigrationsTableQuoted, err = strconv.ParseBool(s)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to parse option x-migrations-table-quoted: %w\", err)\n\t\t}\n\t}\n\tif (len(migrationsTable) > 0) && (migrationsTableQuoted) && ((migrationsTable[0] != '\"') || (migrationsTable[len(migrationsTable)-1] != '\"')) {\n\t\treturn nil, fmt.Errorf(\"x-migrations-table must be quoted (for instance '\\\"migrate\\\".\\\"schema_migrations\\\"') when x-migrations-table-quoted is enabled, current value is: %s\", migrationsTable)\n\t}\n\n\tstatementTimeoutString := purl.Query().Get(\"x-statement-timeout\")\n\tstatementTimeout := 0\n\tif statementTimeoutString != \"\" {\n\t\tstatementTimeout, err = strconv.Atoi(statementTimeoutString)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tmultiStatementMaxSize := DefaultMultiStatementMaxSize\n\tif s := purl.Query().Get(\"x-multi-statement-max-size\"); len(s) > 0 {\n\t\tmultiStatementMaxSize, err = strconv.Atoi(s)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif multiStatementMaxSize <= 0 {\n\t\t\tmultiStatementMaxSize = DefaultMultiStatementMaxSize\n\t\t}\n\t}\n\n\tmultiStatementEnabled := false\n\tif s := purl.Query().Get(\"x-multi-statement\"); len(s) > 0 {\n\t\tmultiStatementEnabled, err = strconv.ParseBool(s)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to parse option x-multi-statement: %w\", err)\n\t\t}\n\t}\n\n\tlockStrategy := purl.Query().Get(\"x-lock-strategy\")\n\tlockTable := purl.Query().Get(\"x-lock-table\")\n\n\tpx, err := WithInstance(db, &Config{\n\t\tDatabaseName:          purl.Path,\n\t\tMigrationsTable:       migrationsTable,\n\t\tMigrationsTableQuoted: migrationsTableQuoted,\n\t\tStatementTimeout:      time.Duration(statementTimeout) * time.Millisecond,\n\t\tMultiStatementEnabled: multiStatementEnabled,\n\t\tMultiStatementMaxSize: multiStatementMaxSize,\n\t\tLockStrategy:          lockStrategy,\n\t\tLockTable:             lockTable,\n\t})\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\nfunc (p *Postgres) Close() error {\n\tconnErr := p.conn.Close()\n\tdbErr := p.db.Close()\n\tif connErr != nil || dbErr != nil {\n\t\treturn fmt.Errorf(\"conn: %v, db: %v\", connErr, dbErr)\n\t}\n\treturn nil\n}\n\nfunc (p *Postgres) Lock() error {\n\treturn database.CasRestoreOnErr(&p.isLocked, false, true, database.ErrLocked, func() error {\n\t\tswitch p.config.LockStrategy {\n\t\tcase LockStrategyAdvisory:\n\t\t\treturn p.applyAdvisoryLock()\n\t\tcase LockStrategyTable:\n\t\t\treturn p.applyTableLock()\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unknown lock strategy \\\"%s\\\"\", p.config.LockStrategy)\n\t\t}\n\t})\n}\n\nfunc (p *Postgres) Unlock() error {\n\treturn database.CasRestoreOnErr(&p.isLocked, true, false, database.ErrNotLocked, func() error {\n\t\tswitch p.config.LockStrategy {\n\t\tcase LockStrategyAdvisory:\n\t\t\treturn p.releaseAdvisoryLock()\n\t\tcase LockStrategyTable:\n\t\t\treturn p.releaseTableLock()\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unknown lock strategy \\\"%s\\\"\", p.config.LockStrategy)\n\t\t}\n\t})\n}\n\n// https://www.postgresql.org/docs/9.6/static/explicit-locking.html#ADVISORY-LOCKS\nfunc (p *Postgres) applyAdvisoryLock() error {\n\taid, err := database.GenerateAdvisoryLockId(p.config.DatabaseName, p.config.migrationsSchemaName, p.config.migrationsTableName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// This will wait indefinitely until the lock can be acquired.\n\tquery := `SELECT pg_advisory_lock($1)`\n\tif _, err := p.conn.ExecContext(context.Background(), query, aid); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"try lock failed\", Query: []byte(query)}\n\t}\n\treturn nil\n}\n\nfunc (p *Postgres) applyTableLock() error {\n\ttx, err := p.conn.BeginTx(context.Background(), &sql.TxOptions{})\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\tdefer func() {\n\t\terrRollback := tx.Rollback()\n\t\tif errRollback != nil {\n\t\t\terr = errors.Join(err, errRollback)\n\t\t}\n\t}()\n\n\taid, err := database.GenerateAdvisoryLockId(p.config.DatabaseName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tquery := \"SELECT * FROM \" + pq.QuoteIdentifier(p.config.LockTable) + \" WHERE lock_id = $1\"\n\trows, err := tx.Query(query, aid)\n\tif err != nil {\n\t\treturn database.Error{OrigErr: err, Err: \"failed to fetch migration lock\", Query: []byte(query)}\n\t}\n\n\tdefer func() {\n\t\tif errClose := rows.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\t// If row exists at all, lock is present\n\tlocked := rows.Next()\n\tif locked {\n\t\treturn database.ErrLocked\n\t}\n\n\tquery = \"INSERT INTO \" + pq.QuoteIdentifier(p.config.LockTable) + \" (lock_id) VALUES ($1)\"\n\tif _, err := tx.Exec(query, aid); err != nil {\n\t\treturn database.Error{OrigErr: err, Err: \"failed to set migration lock\", Query: []byte(query)}\n\t}\n\n\treturn tx.Commit()\n}\n\nfunc (p *Postgres) releaseAdvisoryLock() error {\n\taid, err := database.GenerateAdvisoryLockId(p.config.DatabaseName, p.config.migrationsSchemaName, p.config.migrationsTableName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tquery := `SELECT pg_advisory_unlock($1)`\n\tif _, err := p.conn.ExecContext(context.Background(), query, aid); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn nil\n}\n\nfunc (p *Postgres) releaseTableLock() error {\n\taid, err := database.GenerateAdvisoryLockId(p.config.DatabaseName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tquery := \"DELETE FROM \" + pq.QuoteIdentifier(p.config.LockTable) + \" WHERE lock_id = $1\"\n\tif _, err := p.db.Exec(query, aid); err != nil {\n\t\treturn database.Error{OrigErr: err, Err: \"failed to release migration lock\", Query: []byte(query)}\n\t}\n\n\treturn nil\n}\n\nfunc (p *Postgres) Run(migration io.Reader) error {\n\tif p.config.MultiStatementEnabled {\n\t\tvar err error\n\t\tif e := multistmt.Parse(migration, multiStmtDelimiter, p.config.MultiStatementMaxSize, func(m []byte) bool {\n\t\t\tif err = p.runStatement(m); err != nil {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t}); e != nil {\n\t\t\treturn e\n\t\t}\n\t\treturn err\n\t}\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn p.runStatement(migr)\n}\n\nfunc (p *Postgres) runStatement(statement []byte) error {\n\tctx := context.Background()\n\tif p.config.StatementTimeout != 0 {\n\t\tvar cancel context.CancelFunc\n\t\tctx, cancel = context.WithTimeout(ctx, p.config.StatementTimeout)\n\t\tdefer cancel()\n\t}\n\tquery := string(statement)\n\tif strings.TrimSpace(query) == \"\" {\n\t\treturn nil\n\t}\n\tif _, err := p.conn.ExecContext(ctx, query); err != nil {\n\n\t\tif pgErr, ok := err.(*pgconn.PgError); ok {\n\t\t\tvar line uint\n\t\t\tvar col uint\n\t\t\tvar lineColOK bool\n\t\t\tline, col, lineColOK = computeLineFromPos(query, int(pgErr.Position))\n\t\t\tmessage := fmt.Sprintf(\"migration failed: %s\", pgErr.Message)\n\t\t\tif lineColOK {\n\t\t\t\tmessage = fmt.Sprintf(\"%s (column %d)\", message, col)\n\t\t\t}\n\t\t\tif pgErr.Detail != \"\" {\n\t\t\t\tmessage = fmt.Sprintf(\"%s, %s\", message, pgErr.Detail)\n\t\t\t}\n\t\t\treturn database.Error{OrigErr: err, Err: message, Query: statement, Line: line}\n\t\t}\n\t\treturn database.Error{OrigErr: err, Err: \"migration failed\", Query: statement}\n\t}\n\treturn nil\n}\n\nfunc computeLineFromPos(s string, pos int) (line uint, col uint, ok bool) {\n\t// replace crlf with lf\n\ts = strings.ReplaceAll(s, \"\\r\\n\", \"\\n\")\n\t// pg docs: pos uses index 1 for the first character, and positions are measured in characters not bytes\n\trunes := []rune(s)\n\tif pos > len(runes) {\n\t\treturn 0, 0, false\n\t}\n\tsel := runes[:pos]\n\tline = uint(runesCount(sel, newLine) + 1)\n\tcol = uint(pos - 1 - runesLastIndex(sel, newLine))\n\treturn line, col, true\n}\n\nconst newLine = '\\n'\n\nfunc runesCount(input []rune, target rune) int {\n\tvar count int\n\tfor _, r := range input {\n\t\tif r == target {\n\t\t\tcount++\n\t\t}\n\t}\n\treturn count\n}\n\nfunc runesLastIndex(input []rune, target rune) int {\n\tfor i := len(input) - 1; i >= 0; i-- {\n\t\tif input[i] == target {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (p *Postgres) SetVersion(version int, dirty bool) error {\n\ttx, err := p.conn.BeginTx(context.Background(), &sql.TxOptions{})\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\n\tquery := `TRUNCATE ` + quoteIdentifier(p.config.migrationsSchemaName) + `.` + quoteIdentifier(p.config.migrationsTableName)\n\tif _, err := tx.Exec(query); err != nil {\n\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\terr = errors.Join(err, errRollback)\n\t\t}\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\t// Also re-write the schema version for nil dirty versions to prevent\n\t// empty schema version for failed down migration on the first migration\n\t// See: https://github.com/golang-migrate/migrate/issues/330\n\tif version >= 0 || (version == database.NilVersion && dirty) {\n\t\tquery = `INSERT INTO ` + quoteIdentifier(p.config.migrationsSchemaName) + `.` + quoteIdentifier(p.config.migrationsTableName) + ` (version, dirty) VALUES ($1, $2)`\n\t\tif _, err := tx.Exec(query, version, dirty); err != nil {\n\t\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\t\terr = errors.Join(err, errRollback)\n\t\t\t}\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction commit failed\"}\n\t}\n\n\treturn nil\n}\n\nfunc (p *Postgres) Version() (version int, dirty bool, err error) {\n\tquery := `SELECT version, dirty FROM ` + quoteIdentifier(p.config.migrationsSchemaName) + `.` + quoteIdentifier(p.config.migrationsTableName) + ` LIMIT 1`\n\terr = p.conn.QueryRowContext(context.Background(), query).Scan(&version, &dirty)\n\tswitch {\n\tcase err == sql.ErrNoRows:\n\t\treturn database.NilVersion, false, nil\n\n\tcase err != nil:\n\t\tif e, ok := err.(*pgconn.PgError); ok {\n\t\t\tif e.SQLState() == pgerrcode.UndefinedTable {\n\t\t\t\treturn database.NilVersion, false, nil\n\t\t\t}\n\t\t}\n\t\treturn 0, false, &database.Error{OrigErr: err, Query: []byte(query)}\n\n\tdefault:\n\t\treturn version, dirty, nil\n\t}\n}\n\nfunc (p *Postgres) Drop() (err error) {\n\t// select all tables in current schema\n\tquery := `SELECT table_name FROM information_schema.tables WHERE table_schema=(SELECT current_schema()) AND table_type='BASE TABLE'`\n\ttables, err := p.conn.QueryContext(context.Background(), query)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tdefer func() {\n\t\tif errClose := tables.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\t// delete one table after another\n\ttableNames := make([]string, 0)\n\tfor tables.Next() {\n\t\tvar tableName string\n\t\tif err := tables.Scan(&tableName); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// do not drop lock table\n\t\tif tableName == p.config.LockTable && p.config.LockStrategy == LockStrategyTable {\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(tableName) > 0 {\n\t\t\ttableNames = append(tableNames, tableName)\n\t\t}\n\t}\n\tif err := tables.Err(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tif len(tableNames) > 0 {\n\t\t// delete one by one ...\n\t\tfor _, t := range tableNames {\n\t\t\tquery = `DROP TABLE IF EXISTS ` + quoteIdentifier(t) + ` CASCADE`\n\t\t\tif _, err := p.conn.ExecContext(context.Background(), query); err != nil {\n\t\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database, which deviates from the usual\n// convention of \"caller locks\" in the Postgres type.\nfunc (p *Postgres) ensureVersionTable() (err error) {\n\tif err = p.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := p.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\t// This block checks whether the `MigrationsTable` already exists. This is useful because it allows read only postgres\n\t// users to also check the current version of the schema. Previously, even if `MigrationsTable` existed, the\n\t// `CREATE TABLE IF NOT EXISTS...` query would fail because the user does not have the CREATE permission.\n\t// Taken from https://github.com/mattes/migrate/blob/master/database/postgres/postgres.go#L258\n\tquery := `SELECT COUNT(1) FROM information_schema.tables WHERE table_schema = $1 AND table_name = $2 LIMIT 1`\n\trow := p.conn.QueryRowContext(context.Background(), query, p.config.migrationsSchemaName, p.config.migrationsTableName)\n\n\tvar count int\n\terr = row.Scan(&count)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tif count == 1 {\n\t\treturn nil\n\t}\n\n\tquery = `CREATE TABLE IF NOT EXISTS ` + quoteIdentifier(p.config.migrationsSchemaName) + `.` + quoteIdentifier(p.config.migrationsTableName) + ` (version bigint not null primary key, dirty boolean not null)`\n\tif _, err = p.conn.ExecContext(context.Background(), query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn nil\n}\n\nfunc (p *Postgres) ensureLockTable() error {\n\tif p.config.LockStrategy != LockStrategyTable {\n\t\treturn nil\n\t}\n\n\tvar count int\n\tquery := `SELECT COUNT(1) FROM information_schema.tables WHERE table_name = $1 AND table_schema = (SELECT current_schema()) LIMIT 1`\n\tif err := p.db.QueryRow(query, p.config.LockTable).Scan(&count); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tif count == 1 {\n\t\treturn nil\n\t}\n\n\tquery = `CREATE TABLE ` + pq.QuoteIdentifier(p.config.LockTable) + ` (lock_id BIGINT NOT NULL PRIMARY KEY)`\n\tif _, err := p.db.Exec(query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn nil\n}\n\n// Copied from lib/pq implementation: https://github.com/lib/pq/blob/v1.9.0/conn.go#L1611\nfunc quoteIdentifier(name string) string {\n\tend := strings.IndexRune(name, 0)\n\tif end > -1 {\n\t\tname = name[:end]\n\t}\n\treturn `\"` + strings.ReplaceAll(name, `\"`, `\"\"`) + `\"`\n}\n"
  },
  {
    "path": "database/pgx/pgx_test.go",
    "content": "package pgx\n\n// error codes https://github.com/jackc/pgerrcode/blob/master/errcode.go\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\tsqldriver \"database/sql/driver\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/dhui/dktest\"\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t\"github.com/golang-migrate/migrate/v4/dktesting\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nconst (\n\tpgPassword = \"postgres\"\n)\n\nvar (\n\topts = dktest.Options{\n\t\tEnv:          map[string]string{\"POSTGRES_PASSWORD\": pgPassword},\n\t\tPortRequired: true, ReadyFunc: isReady}\n\t// Supported versions: https://www.postgresql.org/support/versioning/\n\tspecs = []dktesting.ContainerSpec{\n\t\t{ImageName: \"postgres:13\", Options: opts},\n\t\t{ImageName: \"postgres:14\", Options: opts},\n\t\t{ImageName: \"postgres:15\", Options: opts},\n\t\t{ImageName: \"postgres:16\", Options: opts},\n\t\t{ImageName: \"postgres:17\", Options: opts},\n\t}\n)\n\nfunc pgConnectionString(host, port string, options ...string) string {\n\toptions = append(options, \"sslmode=disable\")\n\treturn fmt.Sprintf(\"postgres://postgres:%s@%s:%s/postgres?%s\", pgPassword, host, port, strings.Join(options, \"&\"))\n}\n\nfunc isReady(ctx context.Context, c dktest.ContainerInfo) bool {\n\tip, port, err := c.FirstPort()\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tdb, err := sql.Open(\"pgx\", pgConnectionString(ip, port))\n\tif err != nil {\n\t\treturn false\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\tlog.Println(\"close error:\", err)\n\t\t}\n\t}()\n\tif err = db.PingContext(ctx); err != nil {\n\t\tswitch err {\n\t\tcase sqldriver.ErrBadConn, io.EOF:\n\t\t\treturn false\n\t\tdefault:\n\t\t\tlog.Println(err)\n\t\t}\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc mustRun(t *testing.T, d database.Driver, statements []string) {\n\tfor _, statement := range statements {\n\t\tif err := d.Run(strings.NewReader(statement)); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc Test(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\t})\n}\n\nfunc TestMigrate(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://./examples/migrations\", \"pgx\", d)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\t})\n}\n\nfunc TestMigrateLockTable(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port, \"x-lock-strategy=table\", \"x-lock-table=lock_table\")\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://./examples/migrations\", \"pgx\", d)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\t})\n}\n\nfunc TestMultipleStatements(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tif err := d.Run(strings.NewReader(\"CREATE TABLE foo (foo text); CREATE TABLE bar (bar text);\")); err != nil {\n\t\t\tt.Fatalf(\"expected err to be nil, got %v\", err)\n\t\t}\n\n\t\t// make sure second table exists\n\t\tvar exists bool\n\t\tif err := d.(*Postgres).conn.QueryRowContext(context.Background(), \"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'bar' AND table_schema = (SELECT current_schema()))\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Fatalf(\"expected table bar to exist\")\n\t\t}\n\t})\n}\n\nfunc TestMultipleStatementsInMultiStatementMode(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port, \"x-multi-statement=true\")\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tif err := d.Run(strings.NewReader(\"CREATE TABLE foo (foo text); CREATE INDEX CONCURRENTLY idx_foo ON foo (foo);\")); err != nil {\n\t\t\tt.Fatalf(\"expected err to be nil, got %v\", err)\n\t\t}\n\n\t\t// make sure created index exists\n\t\tvar exists bool\n\t\tif err := d.(*Postgres).conn.QueryRowContext(context.Background(), \"SELECT EXISTS (SELECT 1 FROM pg_indexes WHERE schemaname = (SELECT current_schema()) AND indexname = 'idx_foo')\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Fatalf(\"expected table bar to exist\")\n\t\t}\n\t})\n}\n\nfunc TestErrorParsing(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\twantErr := `migration failed: syntax error at or near \"TABLEE\" (column 37) in line 1: CREATE TABLE foo ` +\n\t\t\t`(foo text); CREATE TABLEE bar (bar text); (details: ERROR: syntax error at or near \"TABLEE\" (SQLSTATE 42601))`\n\t\tif err := d.Run(strings.NewReader(\"CREATE TABLE foo (foo text); CREATE TABLEE bar (bar text);\")); err == nil {\n\t\t\tt.Fatal(\"expected err but got nil\")\n\t\t} else if err.Error() != wantErr {\n\t\t\tt.Fatalf(\"expected '%s' but got '%s'\", wantErr, err.Error())\n\t\t}\n\t})\n}\n\nfunc TestFilterCustomQuery(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port, \"x-custom=foobar\")\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t})\n}\n\nfunc TestWithSchema(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create foobar schema\n\t\tif err := d.Run(strings.NewReader(\"CREATE SCHEMA foobar AUTHORIZATION postgres\")); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := d.SetVersion(1, false); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// re-connect using that schema\n\t\td2, err := p.Open(pgConnectionString(ip, port, \"search_path=foobar\"))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d2.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\n\t\tversion, _, err := d2.Version()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif version != database.NilVersion {\n\t\t\tt.Fatal(\"expected NilVersion\")\n\t\t}\n\n\t\t// now update version and compare\n\t\tif err := d2.SetVersion(2, false); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tversion, _, err = d2.Version()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif version != 2 {\n\t\t\tt.Fatal(\"expected version 2\")\n\t\t}\n\n\t\t// meanwhile, the public schema still has the other version\n\t\tversion, _, err = d.Version()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif version != 1 {\n\t\t\tt.Fatal(\"expected version 2\")\n\t\t}\n\t})\n}\n\nfunc TestMigrationTableOption(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, _ := p.Open(addr)\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create migrate schema\n\t\tif err := d.Run(strings.NewReader(\"CREATE SCHEMA migrate AUTHORIZATION postgres\")); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// bad unquoted x-migrations-table parameter\n\t\twantErr := \"x-migrations-table must be quoted (for instance '\\\"migrate\\\".\\\"schema_migrations\\\"') when x-migrations-table-quoted is enabled, current value is: migrate.schema_migrations\"\n\t\td, err = p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=migrate.schema_migrations&x-migrations-table-quoted=1\",\n\t\t\tpgPassword, ip, port))\n\t\tif (err != nil) && (err.Error() != wantErr) {\n\t\t\tt.Fatalf(\"expected '%s' but got '%s'\", wantErr, err.Error())\n\t\t}\n\n\t\t// too many quoted x-migrations-table parameters\n\t\twantErr = \"\\\"\\\"migrate\\\".\\\"schema_migrations\\\".\\\"toomany\\\"\\\" MigrationsTable contains too many dot characters\"\n\t\td, err = p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=\\\"migrate\\\".\\\"schema_migrations\\\".\\\"toomany\\\"&x-migrations-table-quoted=1\",\n\t\t\tpgPassword, ip, port))\n\t\tif (err != nil) && (err.Error() != wantErr) {\n\t\t\tt.Fatalf(\"expected '%s' but got '%s'\", wantErr, err.Error())\n\t\t}\n\n\t\t// good quoted x-migrations-table parameter\n\t\td, err = p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=\\\"migrate\\\".\\\"schema_migrations\\\"&x-migrations-table-quoted=1\",\n\t\t\tpgPassword, ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// make sure migrate.schema_migrations table exists\n\t\tvar exists bool\n\t\tif err := d.(*Postgres).conn.QueryRowContext(context.Background(), \"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'schema_migrations' AND table_schema = 'migrate')\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Fatalf(\"expected table migrate.schema_migrations to exist\")\n\t\t}\n\n\t\td, err = p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=migrate.schema_migrations\",\n\t\t\tpgPassword, ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := d.(*Postgres).conn.QueryRowContext(context.Background(), \"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'migrate.schema_migrations' AND table_schema = (SELECT current_schema()))\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Fatalf(\"expected table 'migrate.schema_migrations' to exist\")\n\t\t}\n\n\t})\n}\n\nfunc TestFailToCreateTableWithoutPermissions(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\n\t\t// Check that opening the postgres connection returns NilVersion\n\t\tp := &Postgres{}\n\n\t\td, err := p.Open(addr)\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create user who is not the owner. Although we're concatenating strings in an sql statement it should be fine\n\t\t// since this is a test environment and we're not expecting to the pgPassword to be malicious\n\t\tmustRun(t, d, []string{\n\t\t\t\"CREATE USER not_owner WITH ENCRYPTED PASSWORD '\" + pgPassword + \"'\",\n\t\t\t\"CREATE SCHEMA barfoo AUTHORIZATION postgres\",\n\t\t\t\"GRANT USAGE ON SCHEMA barfoo TO not_owner\",\n\t\t\t\"REVOKE CREATE ON SCHEMA barfoo FROM PUBLIC\",\n\t\t\t\"REVOKE CREATE ON SCHEMA barfoo FROM not_owner\",\n\t\t})\n\n\t\t// re-connect using that schema\n\t\td2, err := p.Open(fmt.Sprintf(\"postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&search_path=barfoo\",\n\t\t\tpgPassword, ip, port))\n\n\t\tdefer func() {\n\t\t\tif d2 == nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err := d2.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\n\t\tvar e *database.Error\n\t\tif !errors.As(err, &e) || err == nil {\n\t\t\tt.Fatal(\"Unexpected error, want permission denied error. Got: \", err)\n\t\t}\n\n\t\tif !strings.Contains(e.OrigErr.Error(), \"permission denied for schema barfoo\") {\n\t\t\tt.Fatal(e)\n\t\t}\n\n\t\t// re-connect using that x-migrations-table and x-migrations-table-quoted\n\t\td2, err = p.Open(fmt.Sprintf(\"postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=\\\"barfoo\\\".\\\"schema_migrations\\\"&x-migrations-table-quoted=1\",\n\t\t\tpgPassword, ip, port))\n\n\t\tif !errors.As(err, &e) || err == nil {\n\t\t\tt.Fatal(\"Unexpected error, want permission denied error. Got: \", err)\n\t\t}\n\n\t\tif !strings.Contains(e.OrigErr.Error(), \"permission denied for schema barfoo\") {\n\t\t\tt.Fatal(e)\n\t\t}\n\t})\n}\n\nfunc TestCheckBeforeCreateTable(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\n\t\t// Check that opening the postgres connection returns NilVersion\n\t\tp := &Postgres{}\n\n\t\td, err := p.Open(addr)\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create user who is not the owner. Although we're concatenating strings in an sql statement it should be fine\n\t\t// since this is a test environment and we're not expecting to the pgPassword to be malicious\n\t\tmustRun(t, d, []string{\n\t\t\t\"CREATE USER not_owner WITH ENCRYPTED PASSWORD '\" + pgPassword + \"'\",\n\t\t\t\"CREATE SCHEMA barfoo AUTHORIZATION postgres\",\n\t\t\t\"GRANT USAGE ON SCHEMA barfoo TO not_owner\",\n\t\t\t\"GRANT CREATE ON SCHEMA barfoo TO not_owner\",\n\t\t})\n\n\t\t// re-connect using that schema\n\t\td2, err := p.Open(fmt.Sprintf(\"postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&search_path=barfoo\",\n\t\t\tpgPassword, ip, port))\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif err := d2.Close(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// revoke privileges\n\t\tmustRun(t, d, []string{\n\t\t\t\"REVOKE CREATE ON SCHEMA barfoo FROM PUBLIC\",\n\t\t\t\"REVOKE CREATE ON SCHEMA barfoo FROM not_owner\",\n\t\t})\n\n\t\t// re-connect using that schema\n\t\td3, err := p.Open(fmt.Sprintf(\"postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&search_path=barfoo\",\n\t\t\tpgPassword, ip, port))\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tversion, _, err := d3.Version()\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif version != database.NilVersion {\n\t\t\tt.Fatal(\"Unexpected version, want database.NilVersion. Got: \", version)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif err := d3.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\t})\n}\n\nfunc TestParallelSchema(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create foo and bar schemas\n\t\tif err := d.Run(strings.NewReader(\"CREATE SCHEMA foo AUTHORIZATION postgres\")); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := d.Run(strings.NewReader(\"CREATE SCHEMA bar AUTHORIZATION postgres\")); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// re-connect using that schemas\n\t\tdfoo, err := p.Open(pgConnectionString(ip, port, \"search_path=foo\"))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := dfoo.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tdbar, err := p.Open(pgConnectionString(ip, port, \"search_path=bar\"))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := dbar.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tif err := dfoo.Lock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif err := dbar.Lock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif err := dbar.Unlock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif err := dfoo.Unlock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc TestPostgres_Lock(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\n\t\tps := d.(*Postgres)\n\n\t\terr = ps.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = ps.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = ps.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = ps.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc TestWithInstance_Concurrent(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// The number of concurrent processes running WithInstance\n\t\tconst concurrency = 30\n\n\t\t// We can instantiate a single database handle because it is\n\t\t// actually a connection pool, and so, each of the below go\n\t\t// routines will have a high probability of using a separate\n\t\t// connection, which is something we want to exercise.\n\t\tdb, err := sql.Open(\"pgx\", pgConnectionString(ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := db.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tdb.SetMaxIdleConns(concurrency)\n\t\tdb.SetMaxOpenConns(concurrency)\n\n\t\tvar wg sync.WaitGroup\n\t\tdefer wg.Wait()\n\n\t\twg.Add(concurrency)\n\t\tfor i := 0; i < concurrency; i++ {\n\t\t\tgo func(i int) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\t_, err := WithInstance(db, &Config{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"process %d error: %s\", i, err)\n\t\t\t\t}\n\t\t\t}(i)\n\t\t}\n\t})\n}\nfunc Test_computeLineFromPos(t *testing.T) {\n\ttestcases := []struct {\n\t\tpos      int\n\t\twantLine uint\n\t\twantCol  uint\n\t\tinput    string\n\t\twantOk   bool\n\t}{\n\t\t{\n\t\t\t15, 2, 6, \"SELECT *\\nFROM foo\", true, // foo table does not exists\n\t\t},\n\t\t{\n\t\t\t16, 3, 6, \"SELECT *\\n\\nFROM foo\", true, // foo table does not exists, empty line\n\t\t},\n\t\t{\n\t\t\t25, 3, 7, \"SELECT *\\nFROM foo\\nWHERE x\", true, // x column error\n\t\t},\n\t\t{\n\t\t\t27, 5, 7, \"SELECT *\\n\\nFROM foo\\n\\nWHERE x\", true, // x column error, empty lines\n\t\t},\n\t\t{\n\t\t\t10, 2, 1, \"SELECT *\\nFROMM foo\", true, // FROMM typo\n\t\t},\n\t\t{\n\t\t\t11, 3, 1, \"SELECT *\\n\\nFROMM foo\", true, // FROMM typo, empty line\n\t\t},\n\t\t{\n\t\t\t17, 2, 8, \"SELECT *\\nFROM foo\", true, // last character\n\t\t},\n\t\t{\n\t\t\t18, 0, 0, \"SELECT *\\nFROM foo\", false, // invalid position\n\t\t},\n\t}\n\tfor i, tc := range testcases {\n\t\tt.Run(\"tc\"+strconv.Itoa(i), func(t *testing.T) {\n\t\t\trun := func(crlf bool, nonASCII bool) {\n\t\t\t\tvar name string\n\t\t\t\tif crlf {\n\t\t\t\t\tname = \"crlf\"\n\t\t\t\t} else {\n\t\t\t\t\tname = \"lf\"\n\t\t\t\t}\n\t\t\t\tif nonASCII {\n\t\t\t\t\tname += \"-nonascii\"\n\t\t\t\t} else {\n\t\t\t\t\tname += \"-ascii\"\n\t\t\t\t}\n\t\t\t\tt.Run(name, func(t *testing.T) {\n\t\t\t\t\tinput := tc.input\n\t\t\t\t\tif crlf {\n\t\t\t\t\t\tinput = strings.ReplaceAll(input, \"\\n\", \"\\r\\n\")\n\t\t\t\t\t}\n\t\t\t\t\tif nonASCII {\n\t\t\t\t\t\tinput = strings.ReplaceAll(input, \"FROM\", \"FRÖM\")\n\t\t\t\t\t}\n\t\t\t\t\tgotLine, gotCol, gotOK := computeLineFromPos(input, tc.pos)\n\n\t\t\t\t\tif tc.wantOk {\n\t\t\t\t\t\tt.Logf(\"pos %d, want %d:%d, %#v\", tc.pos, tc.wantLine, tc.wantCol, input)\n\t\t\t\t\t}\n\n\t\t\t\t\tif gotOK != tc.wantOk {\n\t\t\t\t\t\tt.Fatalf(\"expected ok %v but got %v\", tc.wantOk, gotOK)\n\t\t\t\t\t}\n\t\t\t\t\tif gotLine != tc.wantLine {\n\t\t\t\t\t\tt.Fatalf(\"expected line %d but got %d\", tc.wantLine, gotLine)\n\t\t\t\t\t}\n\t\t\t\t\tif gotCol != tc.wantCol {\n\t\t\t\t\t\tt.Fatalf(\"expected col %d but got %d\", tc.wantCol, gotCol)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t\trun(false, false)\n\t\t\trun(true, false)\n\t\t\trun(false, true)\n\t\t\trun(true, true)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "database/pgx/v5/README.md",
    "content": "# pgx\n\nThis package is for [pgx/v5](https://pkg.go.dev/github.com/jackc/pgx/v5). A backend for the older [pgx/v4](https://pkg.go.dev/github.com/jackc/pgx/v4). is [also available](..).\n\n`pgx5://user:password@host:port/dbname?query`\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |\n| `x-migrations-table-quoted` | `MigrationsTableQuoted` | By default, migrate quotes the migration table for SQL injection safety reasons. This option disable quoting and naively checks that you have quoted the migration table name. e.g. `\"my_schema\".\"schema_migrations\"` |\n| `x-statement-timeout` | `StatementTimeout` | Abort any statement that takes more than the specified number of milliseconds |\n| `x-multi-statement` | `MultiStatementEnabled` | Enable multi-statement execution (default: false) |\n| `x-multi-statement-max-size` | `MultiStatementMaxSize` | Maximum size of single statement in bytes (default: 10MB) |\n| `dbname` | `DatabaseName` | The name of the database to connect to |\n| `search_path` | | This variable specifies the order in which schemas are searched when an object is referenced by a simple name with no schema specified. |\n| `user` | | The user to sign in as |\n| `password` | | The user's password |\n| `host` | | The host to connect to. Values that start with / are for unix domain sockets. (default is localhost) |\n| `port` | | The port to bind to. (default is 5432) |\n| `fallback_application_name` | | An application_name to fall back to if one isn't provided. |\n| `connect_timeout` | | Maximum wait for connection, in seconds. Zero or not specified means wait indefinitely. |\n| `sslcert` | | Cert file location. The file must contain PEM encoded data. |\n| `sslkey` | | Key file location. The file must contain PEM encoded data. |\n| `sslrootcert` | | The location of the root certificate file. The file must contain PEM encoded data. |\n| `sslmode` | | Whether or not to use SSL (disable\\|require\\|verify-ca\\|verify-full) |\n\n\n## Upgrading from v1\n\n1. Write down the current migration version from schema_migrations\n1. `DROP TABLE schema_migrations`\n2. Wrap your existing migrations in transactions ([BEGIN/COMMIT](https://www.postgresql.org/docs/current/static/transaction-iso.html)) if you use multiple statements within one migration.\n3. Download and install the latest migrate version.\n4. Force the current migration version with `migrate force <current_version>`.\n\n## Multi-statement mode\n\nIn PostgreSQL running multiple SQL statements in one `Exec` executes them inside a transaction. Sometimes this\nbehavior is not desirable because some statements can be only run outside of transaction (e.g.\n`CREATE INDEX CONCURRENTLY`). If you want to use `CREATE INDEX CONCURRENTLY` without activating multi-statement mode\nyou have to put such statements in a separate migration files.\n"
  },
  {
    "path": "database/pgx/v5/pgx.go",
    "content": "//go:build go1.9\n\npackage pgx\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t\"github.com/golang-migrate/migrate/v4/database/multistmt\"\n\t\"github.com/jackc/pgerrcode\"\n\t\"github.com/jackc/pgx/v5/pgconn\"\n\t_ \"github.com/jackc/pgx/v5/stdlib\"\n)\n\nfunc init() {\n\tdb := Postgres{}\n\tdatabase.Register(\"pgx5\", &db)\n}\n\nvar (\n\tmultiStmtDelimiter = []byte(\";\")\n\n\tDefaultMigrationsTable       = \"schema_migrations\"\n\tDefaultMultiStatementMaxSize = 10 * 1 << 20 // 10 MB\n)\n\nvar (\n\tErrNilConfig      = fmt.Errorf(\"no config\")\n\tErrNoDatabaseName = fmt.Errorf(\"no database name\")\n\tErrNoSchema       = fmt.Errorf(\"no schema\")\n)\n\ntype Config struct {\n\tMigrationsTable       string\n\tDatabaseName          string\n\tSchemaName            string\n\tmigrationsSchemaName  string\n\tmigrationsTableName   string\n\tStatementTimeout      time.Duration\n\tMigrationsTableQuoted bool\n\tMultiStatementEnabled bool\n\tMultiStatementMaxSize int\n}\n\ntype Postgres struct {\n\t// Locking and unlocking need to use the same connection\n\tconn     *sql.Conn\n\tdb       *sql.DB\n\tisLocked atomic.Bool\n\n\t// Open and WithInstance need to guarantee that config is never nil\n\tconfig *Config\n}\n\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif err := instance.Ping(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif config.DatabaseName == \"\" {\n\t\tquery := `SELECT CURRENT_DATABASE()`\n\t\tvar databaseName string\n\t\tif err := instance.QueryRow(query).Scan(&databaseName); err != nil {\n\t\t\treturn nil, &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\tif len(databaseName) == 0 {\n\t\t\treturn nil, ErrNoDatabaseName\n\t\t}\n\n\t\tconfig.DatabaseName = databaseName\n\t}\n\n\tif config.SchemaName == \"\" {\n\t\tquery := `SELECT CURRENT_SCHEMA()`\n\t\tvar schemaName string\n\t\tif err := instance.QueryRow(query).Scan(&schemaName); err != nil {\n\t\t\treturn nil, &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\tif len(schemaName) == 0 {\n\t\t\treturn nil, ErrNoSchema\n\t\t}\n\n\t\tconfig.SchemaName = schemaName\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tconfig.migrationsSchemaName = config.SchemaName\n\tconfig.migrationsTableName = config.MigrationsTable\n\tif config.MigrationsTableQuoted {\n\t\tre := regexp.MustCompile(`\"(.*?)\"`)\n\t\tresult := re.FindAllStringSubmatch(config.MigrationsTable, -1)\n\t\tconfig.migrationsTableName = result[len(result)-1][1]\n\t\tif len(result) == 2 {\n\t\t\tconfig.migrationsSchemaName = result[0][1]\n\t\t} else if len(result) > 2 {\n\t\t\treturn nil, fmt.Errorf(\"\\\"%s\\\" MigrationsTable contains too many dot characters\", config.MigrationsTable)\n\t\t}\n\t}\n\n\tconn, err := instance.Conn(context.Background())\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpx := &Postgres{\n\t\tconn:   conn,\n\t\tdb:     instance,\n\t\tconfig: config,\n\t}\n\n\tif err := px.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\nfunc (p *Postgres) Open(url string) (database.Driver, error) {\n\tpurl, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Driver is registered as pgx, but connection string must use postgres schema\n\t// when making actual connection\n\t// i.e. pgx://user:password@host:port/db => postgres://user:password@host:port/db\n\tpurl.Scheme = \"postgres\"\n\n\tdb, err := sql.Open(\"pgx/v5\", migrate.FilterCustomQuery(purl).String())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmigrationsTable := purl.Query().Get(\"x-migrations-table\")\n\tmigrationsTableQuoted := false\n\tif s := purl.Query().Get(\"x-migrations-table-quoted\"); len(s) > 0 {\n\t\tmigrationsTableQuoted, err = strconv.ParseBool(s)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to parse option x-migrations-table-quoted: %w\", err)\n\t\t}\n\t}\n\tif (len(migrationsTable) > 0) && (migrationsTableQuoted) && ((migrationsTable[0] != '\"') || (migrationsTable[len(migrationsTable)-1] != '\"')) {\n\t\treturn nil, fmt.Errorf(\"x-migrations-table must be quoted (for instance '\\\"migrate\\\".\\\"schema_migrations\\\"') when x-migrations-table-quoted is enabled, current value is: %s\", migrationsTable)\n\t}\n\n\tstatementTimeoutString := purl.Query().Get(\"x-statement-timeout\")\n\tstatementTimeout := 0\n\tif statementTimeoutString != \"\" {\n\t\tstatementTimeout, err = strconv.Atoi(statementTimeoutString)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tmultiStatementMaxSize := DefaultMultiStatementMaxSize\n\tif s := purl.Query().Get(\"x-multi-statement-max-size\"); len(s) > 0 {\n\t\tmultiStatementMaxSize, err = strconv.Atoi(s)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif multiStatementMaxSize <= 0 {\n\t\t\tmultiStatementMaxSize = DefaultMultiStatementMaxSize\n\t\t}\n\t}\n\n\tmultiStatementEnabled := false\n\tif s := purl.Query().Get(\"x-multi-statement\"); len(s) > 0 {\n\t\tmultiStatementEnabled, err = strconv.ParseBool(s)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to parse option x-multi-statement: %w\", err)\n\t\t}\n\t}\n\n\tpx, err := WithInstance(db, &Config{\n\t\tDatabaseName:          purl.Path,\n\t\tMigrationsTable:       migrationsTable,\n\t\tMigrationsTableQuoted: migrationsTableQuoted,\n\t\tStatementTimeout:      time.Duration(statementTimeout) * time.Millisecond,\n\t\tMultiStatementEnabled: multiStatementEnabled,\n\t\tMultiStatementMaxSize: multiStatementMaxSize,\n\t})\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\nfunc (p *Postgres) Close() error {\n\tconnErr := p.conn.Close()\n\tdbErr := p.db.Close()\n\tif connErr != nil || dbErr != nil {\n\t\treturn fmt.Errorf(\"conn: %v, db: %v\", connErr, dbErr)\n\t}\n\treturn nil\n}\n\n// https://www.postgresql.org/docs/9.6/static/explicit-locking.html#ADVISORY-LOCKS\nfunc (p *Postgres) Lock() error {\n\treturn database.CasRestoreOnErr(&p.isLocked, false, true, database.ErrLocked, func() error {\n\t\taid, err := database.GenerateAdvisoryLockId(p.config.DatabaseName, p.config.migrationsSchemaName, p.config.migrationsTableName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// This will wait indefinitely until the lock can be acquired.\n\t\tquery := `SELECT pg_advisory_lock($1)`\n\t\tif _, err := p.conn.ExecContext(context.Background(), query, aid); err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Err: \"try lock failed\", Query: []byte(query)}\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc (p *Postgres) Unlock() error {\n\treturn database.CasRestoreOnErr(&p.isLocked, true, false, database.ErrNotLocked, func() error {\n\t\taid, err := database.GenerateAdvisoryLockId(p.config.DatabaseName, p.config.migrationsSchemaName, p.config.migrationsTableName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tquery := `SELECT pg_advisory_unlock($1)`\n\t\tif _, err := p.conn.ExecContext(context.Background(), query, aid); err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc (p *Postgres) Run(migration io.Reader) error {\n\tif p.config.MultiStatementEnabled {\n\t\tvar err error\n\t\tif e := multistmt.Parse(migration, multiStmtDelimiter, p.config.MultiStatementMaxSize, func(m []byte) bool {\n\t\t\tif err = p.runStatement(m); err != nil {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t}); e != nil {\n\t\t\treturn e\n\t\t}\n\t\treturn err\n\t}\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn p.runStatement(migr)\n}\n\nfunc (p *Postgres) runStatement(statement []byte) error {\n\tctx := context.Background()\n\tif p.config.StatementTimeout != 0 {\n\t\tvar cancel context.CancelFunc\n\t\tctx, cancel = context.WithTimeout(ctx, p.config.StatementTimeout)\n\t\tdefer cancel()\n\t}\n\tquery := string(statement)\n\tif strings.TrimSpace(query) == \"\" {\n\t\treturn nil\n\t}\n\tif _, err := p.conn.ExecContext(ctx, query); err != nil {\n\n\t\tif pgErr, ok := err.(*pgconn.PgError); ok {\n\t\t\tvar line uint\n\t\t\tvar col uint\n\t\t\tvar lineColOK bool\n\t\t\tline, col, lineColOK = computeLineFromPos(query, int(pgErr.Position))\n\t\t\tmessage := fmt.Sprintf(\"migration failed: %s\", pgErr.Message)\n\t\t\tif lineColOK {\n\t\t\t\tmessage = fmt.Sprintf(\"%s (column %d)\", message, col)\n\t\t\t}\n\t\t\tif pgErr.Detail != \"\" {\n\t\t\t\tmessage = fmt.Sprintf(\"%s, %s\", message, pgErr.Detail)\n\t\t\t}\n\t\t\treturn database.Error{OrigErr: err, Err: message, Query: statement, Line: line}\n\t\t}\n\t\treturn database.Error{OrigErr: err, Err: \"migration failed\", Query: statement}\n\t}\n\treturn nil\n}\n\nfunc computeLineFromPos(s string, pos int) (line uint, col uint, ok bool) {\n\t// replace crlf with lf\n\ts = strings.ReplaceAll(s, \"\\r\\n\", \"\\n\")\n\t// pg docs: pos uses index 1 for the first character, and positions are measured in characters not bytes\n\trunes := []rune(s)\n\tif pos > len(runes) {\n\t\treturn 0, 0, false\n\t}\n\tsel := runes[:pos]\n\tline = uint(runesCount(sel, newLine) + 1)\n\tcol = uint(pos - 1 - runesLastIndex(sel, newLine))\n\treturn line, col, true\n}\n\nconst newLine = '\\n'\n\nfunc runesCount(input []rune, target rune) int {\n\tvar count int\n\tfor _, r := range input {\n\t\tif r == target {\n\t\t\tcount++\n\t\t}\n\t}\n\treturn count\n}\n\nfunc runesLastIndex(input []rune, target rune) int {\n\tfor i := len(input) - 1; i >= 0; i-- {\n\t\tif input[i] == target {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (p *Postgres) SetVersion(version int, dirty bool) error {\n\ttx, err := p.conn.BeginTx(context.Background(), &sql.TxOptions{})\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\n\tquery := `TRUNCATE ` + quoteIdentifier(p.config.migrationsSchemaName) + `.` + quoteIdentifier(p.config.migrationsTableName)\n\tif _, err := tx.Exec(query); err != nil {\n\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\terr = errors.Join(err, errRollback)\n\t\t}\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\t// Also re-write the schema version for nil dirty versions to prevent\n\t// empty schema version for failed down migration on the first migration\n\t// See: https://github.com/golang-migrate/migrate/issues/330\n\tif version >= 0 || (version == database.NilVersion && dirty) {\n\t\tquery = `INSERT INTO ` + quoteIdentifier(p.config.migrationsSchemaName) + `.` + quoteIdentifier(p.config.migrationsTableName) + ` (version, dirty) VALUES ($1, $2)`\n\t\tif _, err := tx.Exec(query, version, dirty); err != nil {\n\t\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\t\terr = errors.Join(err, errRollback)\n\t\t\t}\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction commit failed\"}\n\t}\n\n\treturn nil\n}\n\nfunc (p *Postgres) Version() (version int, dirty bool, err error) {\n\tquery := `SELECT version, dirty FROM ` + quoteIdentifier(p.config.migrationsSchemaName) + `.` + quoteIdentifier(p.config.migrationsTableName) + ` LIMIT 1`\n\terr = p.conn.QueryRowContext(context.Background(), query).Scan(&version, &dirty)\n\tswitch {\n\tcase err == sql.ErrNoRows:\n\t\treturn database.NilVersion, false, nil\n\n\tcase err != nil:\n\t\tif e, ok := err.(*pgconn.PgError); ok {\n\t\t\tif e.SQLState() == pgerrcode.UndefinedTable {\n\t\t\t\treturn database.NilVersion, false, nil\n\t\t\t}\n\t\t}\n\t\treturn 0, false, &database.Error{OrigErr: err, Query: []byte(query)}\n\n\tdefault:\n\t\treturn version, dirty, nil\n\t}\n}\n\nfunc (p *Postgres) Drop() (err error) {\n\t// select all tables in current schema\n\tquery := `SELECT table_name FROM information_schema.tables WHERE table_schema=(SELECT current_schema()) AND table_type='BASE TABLE'`\n\ttables, err := p.conn.QueryContext(context.Background(), query)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tdefer func() {\n\t\tif errClose := tables.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\t// delete one table after another\n\ttableNames := make([]string, 0)\n\tfor tables.Next() {\n\t\tvar tableName string\n\t\tif err := tables.Scan(&tableName); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(tableName) > 0 {\n\t\t\ttableNames = append(tableNames, tableName)\n\t\t}\n\t}\n\tif err := tables.Err(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tif len(tableNames) > 0 {\n\t\t// delete one by one ...\n\t\tfor _, t := range tableNames {\n\t\t\tquery = `DROP TABLE IF EXISTS ` + quoteIdentifier(t) + ` CASCADE`\n\t\t\tif _, err := p.conn.ExecContext(context.Background(), query); err != nil {\n\t\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database, which deviates from the usual\n// convention of \"caller locks\" in the Postgres type.\nfunc (p *Postgres) ensureVersionTable() (err error) {\n\tif err = p.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := p.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\t// This block checks whether the `MigrationsTable` already exists. This is useful because it allows read only postgres\n\t// users to also check the current version of the schema. Previously, even if `MigrationsTable` existed, the\n\t// `CREATE TABLE IF NOT EXISTS...` query would fail because the user does not have the CREATE permission.\n\t// Taken from https://github.com/mattes/migrate/blob/master/database/postgres/postgres.go#L258\n\tquery := `SELECT COUNT(1) FROM information_schema.tables WHERE table_schema = $1 AND table_name = $2 LIMIT 1`\n\trow := p.conn.QueryRowContext(context.Background(), query, p.config.migrationsSchemaName, p.config.migrationsTableName)\n\n\tvar count int\n\terr = row.Scan(&count)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tif count == 1 {\n\t\treturn nil\n\t}\n\n\tquery = `CREATE TABLE IF NOT EXISTS ` + quoteIdentifier(p.config.migrationsSchemaName) + `.` + quoteIdentifier(p.config.migrationsTableName) + ` (version bigint not null primary key, dirty boolean not null)`\n\tif _, err = p.conn.ExecContext(context.Background(), query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn nil\n}\n\n// Copied from lib/pq implementation: https://github.com/lib/pq/blob/v1.9.0/conn.go#L1611\nfunc quoteIdentifier(name string) string {\n\tend := strings.IndexRune(name, 0)\n\tif end > -1 {\n\t\tname = name[:end]\n\t}\n\treturn `\"` + strings.ReplaceAll(name, `\"`, `\"\"`) + `\"`\n}\n"
  },
  {
    "path": "database/pgx/v5/pgx_test.go",
    "content": "package pgx\n\n// error codes https://github.com/jackc/pgerrcode/blob/master/errcode.go\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\tsqldriver \"database/sql/driver\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\n\t\"github.com/dhui/dktest\"\n\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t\"github.com/golang-migrate/migrate/v4/dktesting\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nconst (\n\tpgPassword = \"postgres\"\n)\n\nvar (\n\topts = dktest.Options{\n\t\tEnv:          map[string]string{\"POSTGRES_PASSWORD\": pgPassword},\n\t\tPortRequired: true, ReadyFunc: isReady}\n\t// Supported versions: https://www.postgresql.org/support/versioning/\n\tspecs = []dktesting.ContainerSpec{\n\t\t{ImageName: \"postgres:13\", Options: opts},\n\t\t{ImageName: \"postgres:14\", Options: opts},\n\t\t{ImageName: \"postgres:15\", Options: opts},\n\t\t{ImageName: \"postgres:16\", Options: opts},\n\t\t{ImageName: \"postgres:17\", Options: opts},\n\t}\n)\n\nfunc pgConnectionString(host, port string, options ...string) string {\n\toptions = append(options, \"sslmode=disable\")\n\treturn fmt.Sprintf(\"postgres://postgres:%s@%s:%s/postgres?%s\", pgPassword, host, port, strings.Join(options, \"&\"))\n}\n\nfunc isReady(ctx context.Context, c dktest.ContainerInfo) bool {\n\tip, port, err := c.FirstPort()\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tdb, err := sql.Open(\"pgx\", pgConnectionString(ip, port))\n\tif err != nil {\n\t\treturn false\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\tlog.Println(\"close error:\", err)\n\t\t}\n\t}()\n\tif err = db.PingContext(ctx); err != nil {\n\t\tswitch err {\n\t\tcase sqldriver.ErrBadConn, io.EOF:\n\t\t\treturn false\n\t\tdefault:\n\t\t\tlog.Println(err)\n\t\t}\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc mustRun(t *testing.T, d database.Driver, statements []string) {\n\tfor _, statement := range statements {\n\t\tif err := d.Run(strings.NewReader(statement)); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc Test(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\t})\n}\n\nfunc TestMigrate(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://../examples/migrations\", \"pgx\", d)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\t})\n}\n\nfunc TestMultipleStatements(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tif err := d.Run(strings.NewReader(\"CREATE TABLE foo (foo text); CREATE TABLE bar (bar text);\")); err != nil {\n\t\t\tt.Fatalf(\"expected err to be nil, got %v\", err)\n\t\t}\n\n\t\t// make sure second table exists\n\t\tvar exists bool\n\t\tif err := d.(*Postgres).conn.QueryRowContext(context.Background(), \"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'bar' AND table_schema = (SELECT current_schema()))\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Fatalf(\"expected table bar to exist\")\n\t\t}\n\t})\n}\n\nfunc TestMultipleStatementsInMultiStatementMode(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port, \"x-multi-statement=true\")\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tif err := d.Run(strings.NewReader(\"CREATE TABLE foo (foo text); CREATE INDEX CONCURRENTLY idx_foo ON foo (foo);\")); err != nil {\n\t\t\tt.Fatalf(\"expected err to be nil, got %v\", err)\n\t\t}\n\n\t\t// make sure created index exists\n\t\tvar exists bool\n\t\tif err := d.(*Postgres).conn.QueryRowContext(context.Background(), \"SELECT EXISTS (SELECT 1 FROM pg_indexes WHERE schemaname = (SELECT current_schema()) AND indexname = 'idx_foo')\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Fatalf(\"expected table bar to exist\")\n\t\t}\n\t})\n}\n\nfunc TestErrorParsing(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\twantErr := `migration failed: syntax error at or near \"TABLEE\" (column 37) in line 1: CREATE TABLE foo ` +\n\t\t\t`(foo text); CREATE TABLEE bar (bar text); (details: ERROR: syntax error at or near \"TABLEE\" (SQLSTATE 42601))`\n\t\tif err := d.Run(strings.NewReader(\"CREATE TABLE foo (foo text); CREATE TABLEE bar (bar text);\")); err == nil {\n\t\t\tt.Fatal(\"expected err but got nil\")\n\t\t} else if err.Error() != wantErr {\n\t\t\tt.Fatalf(\"expected '%s' but got '%s'\", wantErr, err.Error())\n\t\t}\n\t})\n}\n\nfunc TestFilterCustomQuery(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port, \"x-custom=foobar\")\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t})\n}\n\nfunc TestWithSchema(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create foobar schema\n\t\tif err := d.Run(strings.NewReader(\"CREATE SCHEMA foobar AUTHORIZATION postgres\")); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := d.SetVersion(1, false); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// re-connect using that schema\n\t\td2, err := p.Open(pgConnectionString(ip, port, \"search_path=foobar\"))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d2.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\n\t\tversion, _, err := d2.Version()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif version != database.NilVersion {\n\t\t\tt.Fatal(\"expected NilVersion\")\n\t\t}\n\n\t\t// now update version and compare\n\t\tif err := d2.SetVersion(2, false); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tversion, _, err = d2.Version()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif version != 2 {\n\t\t\tt.Fatal(\"expected version 2\")\n\t\t}\n\n\t\t// meanwhile, the public schema still has the other version\n\t\tversion, _, err = d.Version()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif version != 1 {\n\t\t\tt.Fatal(\"expected version 2\")\n\t\t}\n\t})\n}\n\nfunc TestMigrationTableOption(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, _ := p.Open(addr)\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create migrate schema\n\t\tif err := d.Run(strings.NewReader(\"CREATE SCHEMA migrate AUTHORIZATION postgres\")); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// bad unquoted x-migrations-table parameter\n\t\twantErr := \"x-migrations-table must be quoted (for instance '\\\"migrate\\\".\\\"schema_migrations\\\"') when x-migrations-table-quoted is enabled, current value is: migrate.schema_migrations\"\n\t\td, err = p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=migrate.schema_migrations&x-migrations-table-quoted=1\",\n\t\t\tpgPassword, ip, port))\n\t\tif (err != nil) && (err.Error() != wantErr) {\n\t\t\tt.Fatalf(\"expected '%s' but got '%s'\", wantErr, err.Error())\n\t\t}\n\n\t\t// too many quoted x-migrations-table parameters\n\t\twantErr = \"\\\"\\\"migrate\\\".\\\"schema_migrations\\\".\\\"toomany\\\"\\\" MigrationsTable contains too many dot characters\"\n\t\td, err = p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=\\\"migrate\\\".\\\"schema_migrations\\\".\\\"toomany\\\"&x-migrations-table-quoted=1\",\n\t\t\tpgPassword, ip, port))\n\t\tif (err != nil) && (err.Error() != wantErr) {\n\t\t\tt.Fatalf(\"expected '%s' but got '%s'\", wantErr, err.Error())\n\t\t}\n\n\t\t// good quoted x-migrations-table parameter\n\t\td, err = p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=\\\"migrate\\\".\\\"schema_migrations\\\"&x-migrations-table-quoted=1\",\n\t\t\tpgPassword, ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// make sure migrate.schema_migrations table exists\n\t\tvar exists bool\n\t\tif err := d.(*Postgres).conn.QueryRowContext(context.Background(), \"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'schema_migrations' AND table_schema = 'migrate')\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Fatalf(\"expected table migrate.schema_migrations to exist\")\n\t\t}\n\n\t\td, err = p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=migrate.schema_migrations\",\n\t\t\tpgPassword, ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := d.(*Postgres).conn.QueryRowContext(context.Background(), \"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'migrate.schema_migrations' AND table_schema = (SELECT current_schema()))\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Fatalf(\"expected table 'migrate.schema_migrations' to exist\")\n\t\t}\n\n\t})\n}\n\nfunc TestFailToCreateTableWithoutPermissions(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\n\t\t// Check that opening the postgres connection returns NilVersion\n\t\tp := &Postgres{}\n\n\t\td, err := p.Open(addr)\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create user who is not the owner. Although we're concatenating strings in an sql statement it should be fine\n\t\t// since this is a test environment and we're not expecting to the pgPassword to be malicious\n\t\tmustRun(t, d, []string{\n\t\t\t\"CREATE USER not_owner WITH ENCRYPTED PASSWORD '\" + pgPassword + \"'\",\n\t\t\t\"CREATE SCHEMA barfoo AUTHORIZATION postgres\",\n\t\t\t\"GRANT USAGE ON SCHEMA barfoo TO not_owner\",\n\t\t\t\"REVOKE CREATE ON SCHEMA barfoo FROM PUBLIC\",\n\t\t\t\"REVOKE CREATE ON SCHEMA barfoo FROM not_owner\",\n\t\t})\n\n\t\t// re-connect using that schema\n\t\td2, err := p.Open(fmt.Sprintf(\"postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&search_path=barfoo\",\n\t\t\tpgPassword, ip, port))\n\n\t\tdefer func() {\n\t\t\tif d2 == nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err := d2.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\n\t\tvar e *database.Error\n\t\tif !errors.As(err, &e) || err == nil {\n\t\t\tt.Fatal(\"Unexpected error, want permission denied error. Got: \", err)\n\t\t}\n\n\t\tif !strings.Contains(e.OrigErr.Error(), \"permission denied for schema barfoo\") {\n\t\t\tt.Fatal(e)\n\t\t}\n\n\t\t// re-connect using that x-migrations-table and x-migrations-table-quoted\n\t\td2, err = p.Open(fmt.Sprintf(\"postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=\\\"barfoo\\\".\\\"schema_migrations\\\"&x-migrations-table-quoted=1\",\n\t\t\tpgPassword, ip, port))\n\n\t\tif !errors.As(err, &e) || err == nil {\n\t\t\tt.Fatal(\"Unexpected error, want permission denied error. Got: \", err)\n\t\t}\n\n\t\tif !strings.Contains(e.OrigErr.Error(), \"permission denied for schema barfoo\") {\n\t\t\tt.Fatal(e)\n\t\t}\n\t})\n}\n\nfunc TestCheckBeforeCreateTable(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\n\t\t// Check that opening the postgres connection returns NilVersion\n\t\tp := &Postgres{}\n\n\t\td, err := p.Open(addr)\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create user who is not the owner. Although we're concatenating strings in an sql statement it should be fine\n\t\t// since this is a test environment and we're not expecting to the pgPassword to be malicious\n\t\tmustRun(t, d, []string{\n\t\t\t\"CREATE USER not_owner WITH ENCRYPTED PASSWORD '\" + pgPassword + \"'\",\n\t\t\t\"CREATE SCHEMA barfoo AUTHORIZATION postgres\",\n\t\t\t\"GRANT USAGE ON SCHEMA barfoo TO not_owner\",\n\t\t\t\"GRANT CREATE ON SCHEMA barfoo TO not_owner\",\n\t\t})\n\n\t\t// re-connect using that schema\n\t\td2, err := p.Open(fmt.Sprintf(\"postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&search_path=barfoo\",\n\t\t\tpgPassword, ip, port))\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif err := d2.Close(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// revoke privileges\n\t\tmustRun(t, d, []string{\n\t\t\t\"REVOKE CREATE ON SCHEMA barfoo FROM PUBLIC\",\n\t\t\t\"REVOKE CREATE ON SCHEMA barfoo FROM not_owner\",\n\t\t})\n\n\t\t// re-connect using that schema\n\t\td3, err := p.Open(fmt.Sprintf(\"postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&search_path=barfoo\",\n\t\t\tpgPassword, ip, port))\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tversion, _, err := d3.Version()\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif version != database.NilVersion {\n\t\t\tt.Fatal(\"Unexpected version, want database.NilVersion. Got: \", version)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif err := d3.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\t})\n}\n\nfunc TestParallelSchema(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create foo and bar schemas\n\t\tif err := d.Run(strings.NewReader(\"CREATE SCHEMA foo AUTHORIZATION postgres\")); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := d.Run(strings.NewReader(\"CREATE SCHEMA bar AUTHORIZATION postgres\")); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// re-connect using that schemas\n\t\tdfoo, err := p.Open(pgConnectionString(ip, port, \"search_path=foo\"))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := dfoo.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tdbar, err := p.Open(pgConnectionString(ip, port, \"search_path=bar\"))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := dbar.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tif err := dfoo.Lock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif err := dbar.Lock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif err := dbar.Unlock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif err := dfoo.Unlock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc TestPostgres_Lock(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\n\t\tps := d.(*Postgres)\n\n\t\terr = ps.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = ps.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = ps.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = ps.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc TestWithInstance_Concurrent(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// The number of concurrent processes running WithInstance\n\t\tconst concurrency = 30\n\n\t\t// We can instantiate a single database handle because it is\n\t\t// actually a connection pool, and so, each of the below go\n\t\t// routines will have a high probability of using a separate\n\t\t// connection, which is something we want to exercise.\n\t\tdb, err := sql.Open(\"pgx\", pgConnectionString(ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := db.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tdb.SetMaxIdleConns(concurrency)\n\t\tdb.SetMaxOpenConns(concurrency)\n\n\t\tvar wg sync.WaitGroup\n\t\tdefer wg.Wait()\n\n\t\twg.Add(concurrency)\n\t\tfor i := 0; i < concurrency; i++ {\n\t\t\tgo func(i int) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\t_, err := WithInstance(db, &Config{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"process %d error: %s\", i, err)\n\t\t\t\t}\n\t\t\t}(i)\n\t\t}\n\t})\n}\nfunc Test_computeLineFromPos(t *testing.T) {\n\ttestcases := []struct {\n\t\tpos      int\n\t\twantLine uint\n\t\twantCol  uint\n\t\tinput    string\n\t\twantOk   bool\n\t}{\n\t\t{\n\t\t\t15, 2, 6, \"SELECT *\\nFROM foo\", true, // foo table does not exists\n\t\t},\n\t\t{\n\t\t\t16, 3, 6, \"SELECT *\\n\\nFROM foo\", true, // foo table does not exists, empty line\n\t\t},\n\t\t{\n\t\t\t25, 3, 7, \"SELECT *\\nFROM foo\\nWHERE x\", true, // x column error\n\t\t},\n\t\t{\n\t\t\t27, 5, 7, \"SELECT *\\n\\nFROM foo\\n\\nWHERE x\", true, // x column error, empty lines\n\t\t},\n\t\t{\n\t\t\t10, 2, 1, \"SELECT *\\nFROMM foo\", true, // FROMM typo\n\t\t},\n\t\t{\n\t\t\t11, 3, 1, \"SELECT *\\n\\nFROMM foo\", true, // FROMM typo, empty line\n\t\t},\n\t\t{\n\t\t\t17, 2, 8, \"SELECT *\\nFROM foo\", true, // last character\n\t\t},\n\t\t{\n\t\t\t18, 0, 0, \"SELECT *\\nFROM foo\", false, // invalid position\n\t\t},\n\t}\n\tfor i, tc := range testcases {\n\t\tt.Run(\"tc\"+strconv.Itoa(i), func(t *testing.T) {\n\t\t\trun := func(crlf bool, nonASCII bool) {\n\t\t\t\tvar name string\n\t\t\t\tif crlf {\n\t\t\t\t\tname = \"crlf\"\n\t\t\t\t} else {\n\t\t\t\t\tname = \"lf\"\n\t\t\t\t}\n\t\t\t\tif nonASCII {\n\t\t\t\t\tname += \"-nonascii\"\n\t\t\t\t} else {\n\t\t\t\t\tname += \"-ascii\"\n\t\t\t\t}\n\t\t\t\tt.Run(name, func(t *testing.T) {\n\t\t\t\t\tinput := tc.input\n\t\t\t\t\tif crlf {\n\t\t\t\t\t\tinput = strings.ReplaceAll(input, \"\\n\", \"\\r\\n\")\n\t\t\t\t\t}\n\t\t\t\t\tif nonASCII {\n\t\t\t\t\t\tinput = strings.ReplaceAll(input, \"FROM\", \"FRÖM\")\n\t\t\t\t\t}\n\t\t\t\t\tgotLine, gotCol, gotOK := computeLineFromPos(input, tc.pos)\n\n\t\t\t\t\tif tc.wantOk {\n\t\t\t\t\t\tt.Logf(\"pos %d, want %d:%d, %#v\", tc.pos, tc.wantLine, tc.wantCol, input)\n\t\t\t\t\t}\n\n\t\t\t\t\tif gotOK != tc.wantOk {\n\t\t\t\t\t\tt.Fatalf(\"expected ok %v but got %v\", tc.wantOk, gotOK)\n\t\t\t\t\t}\n\t\t\t\t\tif gotLine != tc.wantLine {\n\t\t\t\t\t\tt.Fatalf(\"expected line %d but got %d\", tc.wantLine, gotLine)\n\t\t\t\t\t}\n\t\t\t\t\tif gotCol != tc.wantCol {\n\t\t\t\t\t\tt.Fatalf(\"expected col %d but got %d\", tc.wantCol, gotCol)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t\trun(false, false)\n\t\t\trun(true, false)\n\t\t\trun(false, true)\n\t\t\trun(true, true)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "database/postgres/README.md",
    "content": "# postgres\n\n`postgres://user:password@host:port/dbname?query` (`postgresql://` works, too)\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |\n| `x-migrations-table-quoted` | `MigrationsTableQuoted` | By default, migrate quotes the migration table for SQL injection safety reasons. This option disable quoting and naively checks that you have quoted the migration table name. e.g. `\"my_schema\".\"schema_migrations\"` |\n| `x-statement-timeout` | `StatementTimeout` | Abort any statement that takes more than the specified number of milliseconds |\n| `x-multi-statement` | `MultiStatementEnabled` | Enable multi-statement execution (default: false) |\n| `x-multi-statement-max-size` | `MultiStatementMaxSize` | Maximum size of single statement in bytes (default: 10MB) |\n| `dbname` | `DatabaseName` | The name of the database to connect to |\n| `search_path` | | This variable specifies the order in which schemas are searched when an object is referenced by a simple name with no schema specified. |\n| `user` | | The user to sign in as |\n| `password` | | The user's password | \n| `host` | | The host to connect to. Values that start with / are for unix domain sockets. (default is localhost) |\n| `port` | | The port to bind to. (default is 5432) |\n| `fallback_application_name` | | An application_name to fall back to if one isn't provided. |\n| `connect_timeout` | | Maximum wait for connection, in seconds. Zero or not specified means wait indefinitely. |\n| `sslcert` | | Cert file location. The file must contain PEM encoded data. |\n| `sslkey` | | Key file location. The file must contain PEM encoded data. |\n| `sslrootcert` | | The location of the root certificate file. The file must contain PEM encoded data. | \n| `sslmode` | | Whether or not to use SSL (disable\\|require\\|verify-ca\\|verify-full) |\n\n\n## Upgrading from v1\n\n1. Write down the current migration version from schema_migrations\n1. `DROP TABLE schema_migrations`\n2. Wrap your existing migrations in transactions ([BEGIN/COMMIT](https://www.postgresql.org/docs/current/static/transaction-iso.html)) if you use multiple statements within one migration.\n3. Download and install the latest migrate version.\n4. Force the current migration version with `migrate force <current_version>`.\n\n## Multi-statement mode\n\nIn PostgreSQL running multiple SQL statements in one `Exec` executes them inside a transaction. Sometimes this\nbehavior is not desirable because some statements can be only run outside of transaction (e.g.\n`CREATE INDEX CONCURRENTLY`). If you want to use `CREATE INDEX CONCURRENTLY` without activating multi-statement mode\nyou have to put such statements in a separate migration files.\n"
  },
  {
    "path": "database/postgres/TUTORIAL.md",
    "content": "# PostgreSQL tutorial for beginners\n\n## Create/configure database\n\nFor the purpose of this tutorial let's create PostgreSQL database called `example`.\nOur user here is `postgres`, password `password`, and host is `localhost`.\n```\npsql -h localhost -U postgres -w -c \"create database example;\"\n```\nWhen using Migrate CLI we need to pass to database URL. Let's export it to a variable for convenience:\n```\nexport POSTGRESQL_URL='postgres://postgres:password@localhost:5432/example?sslmode=disable'\n```\n`sslmode=disable` means that the connection with our database will not be encrypted. Enabling it is left as an exercise.\n\nYou can find further description of database URLs [here](README.md#database-urls).\n\n## Create migrations\nLet's create table called `users`:\n```\nmigrate create -ext sql -dir db/migrations -seq create_users_table\n```\nIf there were no errors, we should have two files available under `db/migrations` folder:\n- 000001_create_users_table.down.sql\n- 000001_create_users_table.up.sql\n\nNote the `sql` extension that we provided.\n\nIn the `.up.sql` file let's create the table:\n```sql\nCREATE TABLE IF NOT EXISTS users(\n   user_id serial PRIMARY KEY,\n   username VARCHAR (50) UNIQUE NOT NULL,\n   password VARCHAR (50) NOT NULL,\n   email VARCHAR (300) UNIQUE NOT NULL\n);\n```\nAnd in the `.down.sql` let's delete it:\n```sql\nDROP TABLE IF EXISTS users;\n```\nBy adding `IF EXISTS/IF NOT EXISTS` we are making migrations idempotent - you can read more about idempotency in [getting started](../../GETTING_STARTED.md#create-migrations)\n\n## Run migrations\n```\nmigrate -database ${POSTGRESQL_URL} -path db/migrations up\n```\nLet's check if the table was created properly by running `psql example -c \"\\d users\"`.\nThe output you are supposed to see:\n```\n                                    Table \"public.users\"\n  Column  |          Type          |                        Modifiers                        \n----------+------------------------+---------------------------------------------------------\n user_id  | integer                | not null default nextval('users_user_id_seq'::regclass)\n username | character varying(50)  | not null\n password | character varying(50)  | not null\n email    | character varying(300) | not null\nIndexes:\n    \"users_pkey\" PRIMARY KEY, btree (user_id)\n    \"users_email_key\" UNIQUE CONSTRAINT, btree (email)\n    \"users_username_key\" UNIQUE CONSTRAINT, btree (username)\n```\nGreat! Now let's check if running reverse migration also works:\n```\nmigrate -database ${POSTGRESQL_URL} -path db/migrations down\n```\nMake sure to check if your database changed as expected in this case as well.\n\n## Database transactions\n\nTo show database transactions usage, let's create another set of migrations by running:\n```\nmigrate create -ext sql -dir db/migrations -seq add_mood_to_users\n```\nAgain, it should create for us two migrations files:\n- 000002_add_mood_to_users.down.sql\n- 000002_add_mood_to_users.up.sql\n\nIn Postgres, when we want our queries to be done in a transaction, we need to wrap it with `BEGIN` and `COMMIT` commands.\nIn our example, we are going to add a column to our database that can only accept enumerable values or NULL.\nMigration up:\n```sql\nBEGIN;\n\nCREATE TYPE enum_mood AS ENUM (\n\t'happy',\n\t'sad',\n\t'neutral'\n);\nALTER TABLE users ADD COLUMN mood enum_mood;\n\nCOMMIT;\n```\nMigration down:\n```sql\nBEGIN;\n\nALTER TABLE users DROP COLUMN mood;\nDROP TYPE enum_mood;\n\nCOMMIT;\n```\n\nNow we can run our new migration and check the database:\n```\nmigrate -database ${POSTGRESQL_URL} -path db/migrations up\npsql example -c \"\\d users\"\n```\nExpected output:\n```\n                                    Table \"public.users\"\n  Column  |          Type          |                        Modifiers                        \n----------+------------------------+---------------------------------------------------------\n user_id  | integer                | not null default nextval('users_user_id_seq'::regclass)\n username | character varying(50)  | not null\n password | character varying(50)  | not null\n email    | character varying(300) | not null\n mood     | enum_mood              | \nIndexes:\n    \"users_pkey\" PRIMARY KEY, btree (user_id)\n    \"users_email_key\" UNIQUE CONSTRAINT, btree (email)\n    \"users_username_key\" UNIQUE CONSTRAINT, btree (username)\n```\n\n## Optional: Run migrations within your Go app\nHere is a very simple app running migrations for the above configuration:\n```go\nimport (\n\t\"log\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t_ \"github.com/golang-migrate/migrate/v4/database/postgres\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nfunc main() {\n\tm, err := migrate.New(\n\t\t\"file://db/migrations\",\n\t\t\"postgres://postgres:postgres@localhost:5432/example?sslmode=disable\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif err := m.Up(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n```\nYou can find details [here](README.md#use-in-your-go-project)\n\n## Fix issue where migrations run twice\n\nWhen the schema and role names are the same, you might run into issues if you create this schema using migrations.\nThis is caused by the fact that the [default `search_path`](https://www.postgresql.org/docs/current/ddl-schemas.html#DDL-SCHEMAS-PATH) is `\"$user\", public`.\nIn the first run (with an empty database) the migrate table is created in `public`.\nWhen the migrations create the `$user` schema, the next run will store (a new) migrate table in this schema (due to order of schemas in `search_path`) and tries to apply all migrations again (most likely failing).\n\nTo solve this you need to change the default `search_path` by removing the `$user` component, so the migrate table is always stored in the (available) `public` schema.\nThis can be done using the [`search_path` query parameter in the URL](https://github.com/jexia/migrate/blob/fix-postgres-version-table/database/postgres/README.md#postgres).\n\nFor example to force the migrations table in the public schema you can use:\n```\nexport POSTGRESQL_URL='postgres://postgres:password@localhost:5432/example?sslmode=disable&search_path=public'\n```\n\nNote that you need to explicitly add the schema names to the table names in your migrations when you to modify the tables of the non-public schema.\n\nAlternatively you can add the non-public schema manually (before applying the migrations) if that is possible in your case and let the tool store the migrations table in this schema as well.\n"
  },
  {
    "path": "database/postgres/examples/migrations/1085649617_create_users_table.down.sql",
    "content": "DROP TABLE IF EXISTS users;\n"
  },
  {
    "path": "database/postgres/examples/migrations/1085649617_create_users_table.up.sql",
    "content": "CREATE TABLE users (\n  user_id integer unique,\n  name    varchar(40),\n  email   varchar(40)\n);\n"
  },
  {
    "path": "database/postgres/examples/migrations/1185749658_add_city_to_users.down.sql",
    "content": "ALTER TABLE users DROP COLUMN IF EXISTS city;\n"
  },
  {
    "path": "database/postgres/examples/migrations/1185749658_add_city_to_users.up.sql",
    "content": "ALTER TABLE users ADD COLUMN city varchar(100);\n\n\n"
  },
  {
    "path": "database/postgres/examples/migrations/1285849751_add_index_on_user_emails.down.sql",
    "content": "DROP INDEX IF EXISTS users_email_index;\n"
  },
  {
    "path": "database/postgres/examples/migrations/1285849751_add_index_on_user_emails.up.sql",
    "content": "CREATE UNIQUE INDEX CONCURRENTLY users_email_index ON users (email);\n\n-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/postgres/examples/migrations/1385949617_create_books_table.down.sql",
    "content": "DROP TABLE IF EXISTS books;\n"
  },
  {
    "path": "database/postgres/examples/migrations/1385949617_create_books_table.up.sql",
    "content": "CREATE TABLE books (\n  user_id integer,\n  name    varchar(40),\n  author  varchar(40)\n);\n"
  },
  {
    "path": "database/postgres/examples/migrations/1485949617_create_movies_table.down.sql",
    "content": "DROP TABLE IF EXISTS movies;\n"
  },
  {
    "path": "database/postgres/examples/migrations/1485949617_create_movies_table.up.sql",
    "content": "CREATE TABLE movies (\n  user_id   integer,\n  name      varchar(40),\n  director  varchar(40)\n);\n"
  },
  {
    "path": "database/postgres/examples/migrations/1585849751_just_a_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/postgres/examples/migrations/1685849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/postgres/examples/migrations/1785849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/postgres/examples/migrations/1885849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/postgres/postgres.go",
    "content": "//go:build go1.9\n\npackage postgres\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t\"github.com/golang-migrate/migrate/v4/database/multistmt\"\n\t\"github.com/lib/pq\"\n)\n\nfunc init() {\n\tdb := Postgres{}\n\tdatabase.Register(\"postgres\", &db)\n\tdatabase.Register(\"postgresql\", &db)\n}\n\nvar (\n\tmultiStmtDelimiter = []byte(\";\")\n\n\tDefaultMigrationsTable       = \"schema_migrations\"\n\tDefaultMultiStatementMaxSize = 10 * 1 << 20 // 10 MB\n)\n\nvar (\n\tErrNilConfig      = fmt.Errorf(\"no config\")\n\tErrNoDatabaseName = fmt.Errorf(\"no database name\")\n\tErrNoSchema       = fmt.Errorf(\"no schema\")\n\tErrDatabaseDirty  = fmt.Errorf(\"database is dirty\")\n)\n\ntype Config struct {\n\tMigrationsTable       string\n\tMigrationsTableQuoted bool\n\tMultiStatementEnabled bool\n\tDatabaseName          string\n\tSchemaName            string\n\tmigrationsSchemaName  string\n\tmigrationsTableName   string\n\tStatementTimeout      time.Duration\n\tMultiStatementMaxSize int\n}\n\ntype Postgres struct {\n\t// Locking and unlocking need to use the same connection\n\tconn     *sql.Conn\n\tdb       *sql.DB\n\tisLocked atomic.Bool\n\n\t// Open and WithInstance need to guarantee that config is never nil\n\tconfig *Config\n}\n\nfunc WithConnection(ctx context.Context, conn *sql.Conn, config *Config) (*Postgres, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif err := conn.PingContext(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif config.DatabaseName == \"\" {\n\t\tquery := `SELECT CURRENT_DATABASE()`\n\t\tvar databaseName string\n\t\tif err := conn.QueryRowContext(ctx, query).Scan(&databaseName); err != nil {\n\t\t\treturn nil, &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\tif len(databaseName) == 0 {\n\t\t\treturn nil, ErrNoDatabaseName\n\t\t}\n\n\t\tconfig.DatabaseName = databaseName\n\t}\n\n\tif config.SchemaName == \"\" {\n\t\tquery := `SELECT CURRENT_SCHEMA()`\n\t\tvar schemaName sql.NullString\n\t\tif err := conn.QueryRowContext(ctx, query).Scan(&schemaName); err != nil {\n\t\t\treturn nil, &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\tif !schemaName.Valid {\n\t\t\treturn nil, ErrNoSchema\n\t\t}\n\n\t\tconfig.SchemaName = schemaName.String\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tconfig.migrationsSchemaName = config.SchemaName\n\tconfig.migrationsTableName = config.MigrationsTable\n\tif config.MigrationsTableQuoted {\n\t\tre := regexp.MustCompile(`\"(.*?)\"`)\n\t\tresult := re.FindAllStringSubmatch(config.MigrationsTable, -1)\n\t\tconfig.migrationsTableName = result[len(result)-1][1]\n\t\tif len(result) == 2 {\n\t\t\tconfig.migrationsSchemaName = result[0][1]\n\t\t} else if len(result) > 2 {\n\t\t\treturn nil, fmt.Errorf(\"\\\"%s\\\" MigrationsTable contains too many dot characters\", config.MigrationsTable)\n\t\t}\n\t}\n\n\tpx := &Postgres{\n\t\tconn:   conn,\n\t\tconfig: config,\n\t}\n\n\tif err := px.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {\n\tctx := context.Background()\n\n\tif err := instance.Ping(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tconn, err := instance.Conn(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpx, err := WithConnection(ctx, conn, config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpx.db = instance\n\treturn px, nil\n}\n\nfunc (p *Postgres) Open(url string) (database.Driver, error) {\n\tpurl, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdb, err := sql.Open(\"postgres\", migrate.FilterCustomQuery(purl).String())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmigrationsTable := purl.Query().Get(\"x-migrations-table\")\n\tmigrationsTableQuoted := false\n\tif s := purl.Query().Get(\"x-migrations-table-quoted\"); len(s) > 0 {\n\t\tmigrationsTableQuoted, err = strconv.ParseBool(s)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to parse option x-migrations-table-quoted: %w\", err)\n\t\t}\n\t}\n\tif (len(migrationsTable) > 0) && (migrationsTableQuoted) && ((migrationsTable[0] != '\"') || (migrationsTable[len(migrationsTable)-1] != '\"')) {\n\t\treturn nil, fmt.Errorf(\"x-migrations-table must be quoted (for instance '\\\"migrate\\\".\\\"schema_migrations\\\"') when x-migrations-table-quoted is enabled, current value is: %s\", migrationsTable)\n\t}\n\n\tstatementTimeoutString := purl.Query().Get(\"x-statement-timeout\")\n\tstatementTimeout := 0\n\tif statementTimeoutString != \"\" {\n\t\tstatementTimeout, err = strconv.Atoi(statementTimeoutString)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tmultiStatementMaxSize := DefaultMultiStatementMaxSize\n\tif s := purl.Query().Get(\"x-multi-statement-max-size\"); len(s) > 0 {\n\t\tmultiStatementMaxSize, err = strconv.Atoi(s)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif multiStatementMaxSize <= 0 {\n\t\t\tmultiStatementMaxSize = DefaultMultiStatementMaxSize\n\t\t}\n\t}\n\n\tmultiStatementEnabled := false\n\tif s := purl.Query().Get(\"x-multi-statement\"); len(s) > 0 {\n\t\tmultiStatementEnabled, err = strconv.ParseBool(s)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to parse option x-multi-statement: %w\", err)\n\t\t}\n\t}\n\n\tpx, err := WithInstance(db, &Config{\n\t\tDatabaseName:          purl.Path,\n\t\tMigrationsTable:       migrationsTable,\n\t\tMigrationsTableQuoted: migrationsTableQuoted,\n\t\tStatementTimeout:      time.Duration(statementTimeout) * time.Millisecond,\n\t\tMultiStatementEnabled: multiStatementEnabled,\n\t\tMultiStatementMaxSize: multiStatementMaxSize,\n\t})\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\nfunc (p *Postgres) Close() error {\n\tconnErr := p.conn.Close()\n\tvar dbErr error\n\tif p.db != nil {\n\t\tdbErr = p.db.Close()\n\t}\n\n\tif connErr != nil || dbErr != nil {\n\t\treturn fmt.Errorf(\"conn: %v, db: %v\", connErr, dbErr)\n\t}\n\treturn nil\n}\n\n// https://www.postgresql.org/docs/9.6/static/explicit-locking.html#ADVISORY-LOCKS\nfunc (p *Postgres) Lock() error {\n\treturn database.CasRestoreOnErr(&p.isLocked, false, true, database.ErrLocked, func() error {\n\t\taid, err := database.GenerateAdvisoryLockId(p.config.DatabaseName, p.config.migrationsSchemaName, p.config.migrationsTableName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// This will wait indefinitely until the lock can be acquired.\n\t\tquery := `SELECT pg_advisory_lock($1)`\n\t\tif _, err := p.conn.ExecContext(context.Background(), query, aid); err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Err: \"try lock failed\", Query: []byte(query)}\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc (p *Postgres) Unlock() error {\n\treturn database.CasRestoreOnErr(&p.isLocked, true, false, database.ErrNotLocked, func() error {\n\t\taid, err := database.GenerateAdvisoryLockId(p.config.DatabaseName, p.config.migrationsSchemaName, p.config.migrationsTableName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tquery := `SELECT pg_advisory_unlock($1)`\n\t\tif _, err := p.conn.ExecContext(context.Background(), query, aid); err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc (p *Postgres) Run(migration io.Reader) error {\n\tif p.config.MultiStatementEnabled {\n\t\tvar err error\n\t\tif e := multistmt.Parse(migration, multiStmtDelimiter, p.config.MultiStatementMaxSize, func(m []byte) bool {\n\t\t\tif err = p.runStatement(m); err != nil {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t}); e != nil {\n\t\t\treturn e\n\t\t}\n\t\treturn err\n\t}\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn p.runStatement(migr)\n}\n\nfunc (p *Postgres) runStatement(statement []byte) error {\n\tctx := context.Background()\n\tif p.config.StatementTimeout != 0 {\n\t\tvar cancel context.CancelFunc\n\t\tctx, cancel = context.WithTimeout(ctx, p.config.StatementTimeout)\n\t\tdefer cancel()\n\t}\n\tquery := string(statement)\n\tif strings.TrimSpace(query) == \"\" {\n\t\treturn nil\n\t}\n\tif _, err := p.conn.ExecContext(ctx, query); err != nil {\n\t\tif pgErr, ok := err.(*pq.Error); ok {\n\t\t\tvar line uint\n\t\t\tvar col uint\n\t\t\tvar lineColOK bool\n\t\t\tif pgErr.Position != \"\" {\n\t\t\t\tif pos, err := strconv.ParseUint(pgErr.Position, 10, 64); err == nil {\n\t\t\t\t\tline, col, lineColOK = computeLineFromPos(query, int(pos))\n\t\t\t\t}\n\t\t\t}\n\t\t\tmessage := fmt.Sprintf(\"migration failed: %s\", pgErr.Message)\n\t\t\tif lineColOK {\n\t\t\t\tmessage = fmt.Sprintf(\"%s (column %d)\", message, col)\n\t\t\t}\n\t\t\tif pgErr.Detail != \"\" {\n\t\t\t\tmessage = fmt.Sprintf(\"%s, %s\", message, pgErr.Detail)\n\t\t\t}\n\t\t\treturn database.Error{OrigErr: err, Err: message, Query: statement, Line: line}\n\t\t}\n\t\treturn database.Error{OrigErr: err, Err: \"migration failed\", Query: statement}\n\t}\n\treturn nil\n}\n\nfunc computeLineFromPos(s string, pos int) (line uint, col uint, ok bool) {\n\t// replace crlf with lf\n\ts = strings.ReplaceAll(s, \"\\r\\n\", \"\\n\")\n\t// pg docs: pos uses index 1 for the first character, and positions are measured in characters not bytes\n\trunes := []rune(s)\n\tif pos > len(runes) {\n\t\treturn 0, 0, false\n\t}\n\tsel := runes[:pos]\n\tline = uint(runesCount(sel, newLine) + 1)\n\tcol = uint(pos - 1 - runesLastIndex(sel, newLine))\n\treturn line, col, true\n}\n\nconst newLine = '\\n'\n\nfunc runesCount(input []rune, target rune) int {\n\tvar count int\n\tfor _, r := range input {\n\t\tif r == target {\n\t\t\tcount++\n\t\t}\n\t}\n\treturn count\n}\n\nfunc runesLastIndex(input []rune, target rune) int {\n\tfor i := len(input) - 1; i >= 0; i-- {\n\t\tif input[i] == target {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (p *Postgres) SetVersion(version int, dirty bool) error {\n\ttx, err := p.conn.BeginTx(context.Background(), &sql.TxOptions{})\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\n\tquery := `TRUNCATE ` + pq.QuoteIdentifier(p.config.migrationsSchemaName) + `.` + pq.QuoteIdentifier(p.config.migrationsTableName)\n\tif _, err := tx.Exec(query); err != nil {\n\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\terr = errors.Join(err, errRollback)\n\t\t}\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\t// Also re-write the schema version for nil dirty versions to prevent\n\t// empty schema version for failed down migration on the first migration\n\t// See: https://github.com/golang-migrate/migrate/issues/330\n\tif version >= 0 || (version == database.NilVersion && dirty) {\n\t\tquery = `INSERT INTO ` + pq.QuoteIdentifier(p.config.migrationsSchemaName) + `.` + pq.QuoteIdentifier(p.config.migrationsTableName) + ` (version, dirty) VALUES ($1, $2)`\n\t\tif _, err := tx.Exec(query, version, dirty); err != nil {\n\t\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\t\terr = errors.Join(err, errRollback)\n\t\t\t}\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction commit failed\"}\n\t}\n\n\treturn nil\n}\n\nfunc (p *Postgres) Version() (version int, dirty bool, err error) {\n\tquery := `SELECT version, dirty FROM ` + pq.QuoteIdentifier(p.config.migrationsSchemaName) + `.` + pq.QuoteIdentifier(p.config.migrationsTableName) + ` LIMIT 1`\n\terr = p.conn.QueryRowContext(context.Background(), query).Scan(&version, &dirty)\n\tswitch {\n\tcase err == sql.ErrNoRows:\n\t\treturn database.NilVersion, false, nil\n\n\tcase err != nil:\n\t\tif e, ok := err.(*pq.Error); ok {\n\t\t\tif e.Code.Name() == \"undefined_table\" {\n\t\t\t\treturn database.NilVersion, false, nil\n\t\t\t}\n\t\t}\n\t\treturn 0, false, &database.Error{OrigErr: err, Query: []byte(query)}\n\n\tdefault:\n\t\treturn version, dirty, nil\n\t}\n}\n\nfunc (p *Postgres) Drop() (err error) {\n\t// select all tables in current schema\n\tquery := `SELECT table_name FROM information_schema.tables WHERE table_schema=(SELECT current_schema()) AND table_type='BASE TABLE'`\n\ttables, err := p.conn.QueryContext(context.Background(), query)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tdefer func() {\n\t\tif errClose := tables.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\t// delete one table after another\n\ttableNames := make([]string, 0)\n\tfor tables.Next() {\n\t\tvar tableName string\n\t\tif err := tables.Scan(&tableName); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(tableName) > 0 {\n\t\t\ttableNames = append(tableNames, tableName)\n\t\t}\n\t}\n\tif err := tables.Err(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tif len(tableNames) > 0 {\n\t\t// delete one by one ...\n\t\tfor _, t := range tableNames {\n\t\t\tquery = `DROP TABLE IF EXISTS ` + pq.QuoteIdentifier(t) + ` CASCADE`\n\t\t\tif _, err := p.conn.ExecContext(context.Background(), query); err != nil {\n\t\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database, which deviates from the usual\n// convention of \"caller locks\" in the Postgres type.\nfunc (p *Postgres) ensureVersionTable() (err error) {\n\tif err = p.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := p.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\t// This block checks whether the `MigrationsTable` already exists. This is useful because it allows read only postgres\n\t// users to also check the current version of the schema. Previously, even if `MigrationsTable` existed, the\n\t// `CREATE TABLE IF NOT EXISTS...` query would fail because the user does not have the CREATE permission.\n\t// Taken from https://github.com/mattes/migrate/blob/master/database/postgres/postgres.go#L258\n\tquery := `SELECT COUNT(1) FROM information_schema.tables WHERE table_schema = $1 AND table_name = $2 LIMIT 1`\n\trow := p.conn.QueryRowContext(context.Background(), query, p.config.migrationsSchemaName, p.config.migrationsTableName)\n\n\tvar count int\n\terr = row.Scan(&count)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tif count == 1 {\n\t\treturn nil\n\t}\n\n\tquery = `CREATE TABLE IF NOT EXISTS ` + pq.QuoteIdentifier(p.config.migrationsSchemaName) + `.` + pq.QuoteIdentifier(p.config.migrationsTableName) + ` (version bigint not null primary key, dirty boolean not null)`\n\tif _, err = p.conn.ExecContext(context.Background(), query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "database/postgres/postgres_test.go",
    "content": "package postgres\n\n// error codes https://github.com/lib/pq/blob/master/error.go\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\tsqldriver \"database/sql/driver\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\n\t\"github.com/dhui/dktest\"\n\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t\"github.com/golang-migrate/migrate/v4/dktesting\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nconst (\n\tpgPassword = \"postgres\"\n)\n\nvar (\n\topts = dktest.Options{\n\t\tEnv:          map[string]string{\"POSTGRES_PASSWORD\": pgPassword},\n\t\tPortRequired: true, ReadyFunc: isReady}\n\t// Supported versions: https://www.postgresql.org/support/versioning/\n\tspecs = []dktesting.ContainerSpec{\n\t\t{ImageName: \"postgres:13\", Options: opts},\n\t\t{ImageName: \"postgres:14\", Options: opts},\n\t\t{ImageName: \"postgres:15\", Options: opts},\n\t\t{ImageName: \"postgres:16\", Options: opts},\n\t\t{ImageName: \"postgres:17\", Options: opts},\n\t}\n)\n\nfunc pgConnectionString(host, port string, options ...string) string {\n\toptions = append(options, \"sslmode=disable\")\n\treturn fmt.Sprintf(\"postgres://postgres:%s@%s:%s/postgres?%s\", pgPassword, host, port, strings.Join(options, \"&\"))\n}\n\nfunc isReady(ctx context.Context, c dktest.ContainerInfo) bool {\n\tip, port, err := c.FirstPort()\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tdb, err := sql.Open(\"postgres\", pgConnectionString(ip, port))\n\tif err != nil {\n\t\treturn false\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\tlog.Println(\"close error:\", err)\n\t\t}\n\t}()\n\tif err = db.PingContext(ctx); err != nil {\n\t\tswitch err {\n\t\tcase sqldriver.ErrBadConn, io.EOF:\n\t\t\treturn false\n\t\tdefault:\n\t\t\tlog.Println(err)\n\t\t}\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc mustRun(t *testing.T, d database.Driver, statements []string) {\n\tfor _, statement := range statements {\n\t\tif err := d.Run(strings.NewReader(statement)); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc Test(t *testing.T) {\n\tt.Run(\"test\", test)\n\tt.Run(\"testMigrate\", testMigrate)\n\tt.Run(\"testMultipleStatements\", testMultipleStatements)\n\tt.Run(\"testMultipleStatementsInMultiStatementMode\", testMultipleStatementsInMultiStatementMode)\n\tt.Run(\"testErrorParsing\", testErrorParsing)\n\tt.Run(\"testFilterCustomQuery\", testFilterCustomQuery)\n\tt.Run(\"testWithSchema\", testWithSchema)\n\tt.Run(\"testMigrationTableOption\", testMigrationTableOption)\n\tt.Run(\"testFailToCreateTableWithoutPermissions\", testFailToCreateTableWithoutPermissions)\n\tt.Run(\"testCheckBeforeCreateTable\", testCheckBeforeCreateTable)\n\tt.Run(\"testParallelSchema\", testParallelSchema)\n\tt.Run(\"testPostgresLock\", testPostgresLock)\n\tt.Run(\"testWithInstanceConcurrent\", testWithInstanceConcurrent)\n\tt.Run(\"testWithConnection\", testWithConnection)\n\n\tt.Cleanup(func() {\n\t\tfor _, spec := range specs {\n\t\t\tt.Log(\"Cleaning up \", spec.ImageName)\n\t\t\tif err := spec.Cleanup(); err != nil {\n\t\t\t\tt.Error(\"Error removing \", spec.ImageName, \"error:\", err)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc test(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\t})\n}\n\nfunc testMigrate(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://./examples/migrations\", \"postgres\", d)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\t})\n}\n\nfunc testMultipleStatements(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tif err := d.Run(strings.NewReader(\"CREATE TABLE foo (foo text); CREATE TABLE bar (bar text);\")); err != nil {\n\t\t\tt.Fatalf(\"expected err to be nil, got %v\", err)\n\t\t}\n\n\t\t// make sure second table exists\n\t\tvar exists bool\n\t\tif err := d.(*Postgres).conn.QueryRowContext(context.Background(), \"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'bar' AND table_schema = (SELECT current_schema()))\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Fatalf(\"expected table bar to exist\")\n\t\t}\n\t})\n}\n\nfunc testMultipleStatementsInMultiStatementMode(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port, \"x-multi-statement=true\")\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tif err := d.Run(strings.NewReader(\"CREATE TABLE foo (foo text); CREATE INDEX CONCURRENTLY idx_foo ON foo (foo);\")); err != nil {\n\t\t\tt.Fatalf(\"expected err to be nil, got %v\", err)\n\t\t}\n\n\t\t// make sure created index exists\n\t\tvar exists bool\n\t\tif err := d.(*Postgres).conn.QueryRowContext(context.Background(), \"SELECT EXISTS (SELECT 1 FROM pg_indexes WHERE schemaname = (SELECT current_schema()) AND indexname = 'idx_foo')\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Fatalf(\"expected table bar to exist\")\n\t\t}\n\t})\n}\n\nfunc testErrorParsing(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\twantErr := `migration failed: syntax error at or near \"TABLEE\" (column 37) in line 1: CREATE TABLE foo ` +\n\t\t\t`(foo text); CREATE TABLEE bar (bar text); (details: pq: syntax error at or near \"TABLEE\")`\n\t\tif err := d.Run(strings.NewReader(\"CREATE TABLE foo (foo text); CREATE TABLEE bar (bar text);\")); err == nil {\n\t\t\tt.Fatal(\"expected err but got nil\")\n\t\t} else if err.Error() != wantErr {\n\t\t\tt.Fatalf(\"expected '%s' but got '%s'\", wantErr, err.Error())\n\t\t}\n\t})\n}\n\nfunc testFilterCustomQuery(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-custom=foobar\",\n\t\t\tpgPassword, ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t})\n}\n\nfunc testWithSchema(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create foobar schema\n\t\tif err := d.Run(strings.NewReader(\"CREATE SCHEMA foobar AUTHORIZATION postgres\")); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := d.SetVersion(1, false); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// re-connect using that schema\n\t\td2, err := p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&search_path=foobar\",\n\t\t\tpgPassword, ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d2.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\n\t\tversion, _, err := d2.Version()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif version != database.NilVersion {\n\t\t\tt.Fatal(\"expected NilVersion\")\n\t\t}\n\n\t\t// now update version and compare\n\t\tif err := d2.SetVersion(2, false); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tversion, _, err = d2.Version()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif version != 2 {\n\t\t\tt.Fatal(\"expected version 2\")\n\t\t}\n\n\t\t// meanwhile, the public schema still has the other version\n\t\tversion, _, err = d.Version()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif version != 1 {\n\t\t\tt.Fatal(\"expected version 2\")\n\t\t}\n\t})\n}\n\nfunc testMigrationTableOption(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, _ := p.Open(addr)\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create migrate schema\n\t\tif err := d.Run(strings.NewReader(\"CREATE SCHEMA migrate AUTHORIZATION postgres\")); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// bad unquoted x-migrations-table parameter\n\t\twantErr := \"x-migrations-table must be quoted (for instance '\\\"migrate\\\".\\\"schema_migrations\\\"') when x-migrations-table-quoted is enabled, current value is: migrate.schema_migrations\"\n\t\td, err = p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=migrate.schema_migrations&x-migrations-table-quoted=1\",\n\t\t\tpgPassword, ip, port))\n\t\tif (err != nil) && (err.Error() != wantErr) {\n\t\t\tt.Fatalf(\"expected '%s' but got '%s'\", wantErr, err.Error())\n\t\t}\n\n\t\t// too many quoted x-migrations-table parameters\n\t\twantErr = \"\\\"\\\"migrate\\\".\\\"schema_migrations\\\".\\\"toomany\\\"\\\" MigrationsTable contains too many dot characters\"\n\t\td, err = p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=\\\"migrate\\\".\\\"schema_migrations\\\".\\\"toomany\\\"&x-migrations-table-quoted=1\",\n\t\t\tpgPassword, ip, port))\n\t\tif (err != nil) && (err.Error() != wantErr) {\n\t\t\tt.Fatalf(\"expected '%s' but got '%s'\", wantErr, err.Error())\n\t\t}\n\n\t\t// good quoted x-migrations-table parameter\n\t\td, err = p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=\\\"migrate\\\".\\\"schema_migrations\\\"&x-migrations-table-quoted=1\",\n\t\t\tpgPassword, ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// make sure migrate.schema_migrations table exists\n\t\tvar exists bool\n\t\tif err := d.(*Postgres).conn.QueryRowContext(context.Background(), \"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'schema_migrations' AND table_schema = 'migrate')\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Fatalf(\"expected table migrate.schema_migrations to exist\")\n\t\t}\n\n\t\td, err = p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=migrate.schema_migrations\",\n\t\t\tpgPassword, ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := d.(*Postgres).conn.QueryRowContext(context.Background(), \"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'migrate.schema_migrations' AND table_schema = (SELECT current_schema()))\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Fatalf(\"expected table 'migrate.schema_migrations' to exist\")\n\t\t}\n\n\t})\n}\n\nfunc testFailToCreateTableWithoutPermissions(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\n\t\t// Check that opening the postgres connection returns NilVersion\n\t\tp := &Postgres{}\n\n\t\td, err := p.Open(addr)\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create user who is not the owner. Although we're concatenating strings in an sql statement it should be fine\n\t\t// since this is a test environment and we're not expecting to the pgPassword to be malicious\n\t\tmustRun(t, d, []string{\n\t\t\t\"CREATE USER not_owner WITH ENCRYPTED PASSWORD '\" + pgPassword + \"'\",\n\t\t\t\"CREATE SCHEMA barfoo AUTHORIZATION postgres\",\n\t\t\t\"GRANT USAGE ON SCHEMA barfoo TO not_owner\",\n\t\t\t\"REVOKE CREATE ON SCHEMA barfoo FROM PUBLIC\",\n\t\t\t\"REVOKE CREATE ON SCHEMA barfoo FROM not_owner\",\n\t\t})\n\n\t\t// re-connect using that schema\n\t\td2, err := p.Open(fmt.Sprintf(\"postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&search_path=barfoo\",\n\t\t\tpgPassword, ip, port))\n\n\t\tdefer func() {\n\t\t\tif d2 == nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err := d2.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\n\t\tvar e *database.Error\n\t\tif !errors.As(err, &e) || err == nil {\n\t\t\tt.Fatal(\"Unexpected error, want permission denied error. Got: \", err)\n\t\t}\n\n\t\tif !strings.Contains(e.OrigErr.Error(), \"permission denied for schema barfoo\") {\n\t\t\tt.Fatal(e)\n\t\t}\n\n\t\t// re-connect using that x-migrations-table and x-migrations-table-quoted\n\t\td2, err = p.Open(fmt.Sprintf(\"postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=\\\"barfoo\\\".\\\"schema_migrations\\\"&x-migrations-table-quoted=1\",\n\t\t\tpgPassword, ip, port))\n\n\t\tif !errors.As(err, &e) || err == nil {\n\t\t\tt.Fatal(\"Unexpected error, want permission denied error. Got: \", err)\n\t\t}\n\n\t\tif !strings.Contains(e.OrigErr.Error(), \"permission denied for schema barfoo\") {\n\t\t\tt.Fatal(e)\n\t\t}\n\t})\n}\n\nfunc testCheckBeforeCreateTable(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\n\t\t// Check that opening the postgres connection returns NilVersion\n\t\tp := &Postgres{}\n\n\t\td, err := p.Open(addr)\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create user who is not the owner. Although we're concatenating strings in an sql statement it should be fine\n\t\t// since this is a test environment and we're not expecting to the pgPassword to be malicious\n\t\tmustRun(t, d, []string{\n\t\t\t\"CREATE USER not_owner WITH ENCRYPTED PASSWORD '\" + pgPassword + \"'\",\n\t\t\t\"CREATE SCHEMA barfoo AUTHORIZATION postgres\",\n\t\t\t\"GRANT USAGE ON SCHEMA barfoo TO not_owner\",\n\t\t\t\"GRANT CREATE ON SCHEMA barfoo TO not_owner\",\n\t\t})\n\n\t\t// re-connect using that schema\n\t\td2, err := p.Open(fmt.Sprintf(\"postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&search_path=barfoo\",\n\t\t\tpgPassword, ip, port))\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif err := d2.Close(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// revoke privileges\n\t\tmustRun(t, d, []string{\n\t\t\t\"REVOKE CREATE ON SCHEMA barfoo FROM PUBLIC\",\n\t\t\t\"REVOKE CREATE ON SCHEMA barfoo FROM not_owner\",\n\t\t})\n\n\t\t// re-connect using that schema\n\t\td3, err := p.Open(fmt.Sprintf(\"postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&search_path=barfoo\",\n\t\t\tpgPassword, ip, port))\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tversion, _, err := d3.Version()\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif version != database.NilVersion {\n\t\t\tt.Fatal(\"Unexpected version, want database.NilVersion. Got: \", version)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif err := d3.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\t})\n}\n\nfunc testParallelSchema(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create foo and bar schemas\n\t\tif err := d.Run(strings.NewReader(\"CREATE SCHEMA foo AUTHORIZATION postgres\")); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := d.Run(strings.NewReader(\"CREATE SCHEMA bar AUTHORIZATION postgres\")); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// re-connect using that schemas\n\t\tdfoo, err := p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&search_path=foo\",\n\t\t\tpgPassword, ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := dfoo.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tdbar, err := p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&search_path=bar\",\n\t\t\tpgPassword, ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := dbar.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tif err := dfoo.Lock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif err := dbar.Lock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif err := dbar.Unlock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif err := dfoo.Unlock(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc testPostgresLock(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Postgres{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\n\t\tps := d.(*Postgres)\n\n\t\terr = ps.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = ps.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = ps.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = ps.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc testWithInstanceConcurrent(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// The number of concurrent processes running WithInstance\n\t\tconst concurrency = 30\n\n\t\t// We can instantiate a single database handle because it is\n\t\t// actually a connection pool, and so, each of the below go\n\t\t// routines will have a high probability of using a separate\n\t\t// connection, which is something we want to exercise.\n\t\tdb, err := sql.Open(\"postgres\", pgConnectionString(ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := db.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tdb.SetMaxIdleConns(concurrency)\n\t\tdb.SetMaxOpenConns(concurrency)\n\n\t\tvar wg sync.WaitGroup\n\t\tdefer wg.Wait()\n\n\t\twg.Add(concurrency)\n\t\tfor i := 0; i < concurrency; i++ {\n\t\t\tgo func(i int) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\t_, err := WithInstance(db, &Config{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"process %d error: %s\", i, err)\n\t\t\t\t}\n\t\t\t}(i)\n\t\t}\n\t})\n}\n\nfunc testWithConnection(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdb, err := sql.Open(\"postgres\", pgConnectionString(ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := db.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tctx := context.Background()\n\t\tconn, err := db.Conn(ctx)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tp, err := WithConnection(ctx, conn, &Config{})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif err := p.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tdt.Test(t, p, []byte(\"SELECT 1\"))\n\t})\n}\n\nfunc Test_computeLineFromPos(t *testing.T) {\n\ttestcases := []struct {\n\t\tpos      int\n\t\twantLine uint\n\t\twantCol  uint\n\t\tinput    string\n\t\twantOk   bool\n\t}{\n\t\t{\n\t\t\t15, 2, 6, \"SELECT *\\nFROM foo\", true, // foo table does not exists\n\t\t},\n\t\t{\n\t\t\t16, 3, 6, \"SELECT *\\n\\nFROM foo\", true, // foo table does not exists, empty line\n\t\t},\n\t\t{\n\t\t\t25, 3, 7, \"SELECT *\\nFROM foo\\nWHERE x\", true, // x column error\n\t\t},\n\t\t{\n\t\t\t27, 5, 7, \"SELECT *\\n\\nFROM foo\\n\\nWHERE x\", true, // x column error, empty lines\n\t\t},\n\t\t{\n\t\t\t10, 2, 1, \"SELECT *\\nFROMM foo\", true, // FROMM typo\n\t\t},\n\t\t{\n\t\t\t11, 3, 1, \"SELECT *\\n\\nFROMM foo\", true, // FROMM typo, empty line\n\t\t},\n\t\t{\n\t\t\t17, 2, 8, \"SELECT *\\nFROM foo\", true, // last character\n\t\t},\n\t\t{\n\t\t\t18, 0, 0, \"SELECT *\\nFROM foo\", false, // invalid position\n\t\t},\n\t}\n\tfor i, tc := range testcases {\n\t\tt.Run(\"tc\"+strconv.Itoa(i), func(t *testing.T) {\n\t\t\trun := func(crlf bool, nonASCII bool) {\n\t\t\t\tvar name string\n\t\t\t\tif crlf {\n\t\t\t\t\tname = \"crlf\"\n\t\t\t\t} else {\n\t\t\t\t\tname = \"lf\"\n\t\t\t\t}\n\t\t\t\tif nonASCII {\n\t\t\t\t\tname += \"-nonascii\"\n\t\t\t\t} else {\n\t\t\t\t\tname += \"-ascii\"\n\t\t\t\t}\n\t\t\t\tt.Run(name, func(t *testing.T) {\n\t\t\t\t\tinput := tc.input\n\t\t\t\t\tif crlf {\n\t\t\t\t\t\tinput = strings.ReplaceAll(input, \"\\n\", \"\\r\\n\")\n\t\t\t\t\t}\n\t\t\t\t\tif nonASCII {\n\t\t\t\t\t\tinput = strings.ReplaceAll(input, \"FROM\", \"FRÖM\")\n\t\t\t\t\t}\n\t\t\t\t\tgotLine, gotCol, gotOK := computeLineFromPos(input, tc.pos)\n\n\t\t\t\t\tif tc.wantOk {\n\t\t\t\t\t\tt.Logf(\"pos %d, want %d:%d, %#v\", tc.pos, tc.wantLine, tc.wantCol, input)\n\t\t\t\t\t}\n\n\t\t\t\t\tif gotOK != tc.wantOk {\n\t\t\t\t\t\tt.Fatalf(\"expected ok %v but got %v\", tc.wantOk, gotOK)\n\t\t\t\t\t}\n\t\t\t\t\tif gotLine != tc.wantLine {\n\t\t\t\t\t\tt.Fatalf(\"expected line %d but got %d\", tc.wantLine, gotLine)\n\t\t\t\t\t}\n\t\t\t\t\tif gotCol != tc.wantCol {\n\t\t\t\t\t\tt.Fatalf(\"expected col %d but got %d\", tc.wantCol, gotCol)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t\trun(false, false)\n\t\t\trun(true, false)\n\t\t\trun(false, true)\n\t\t\trun(true, true)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "database/ql/README.md",
    "content": ""
  },
  {
    "path": "database/ql/examples/migrations/33_create_table.down.sql",
    "content": "DROP TABLE IF EXISTS pets;"
  },
  {
    "path": "database/ql/examples/migrations/33_create_table.up.sql",
    "content": "CREATE TABLE pets (\n  name string\n);"
  },
  {
    "path": "database/ql/examples/migrations/44_alter_table.down.sql",
    "content": "DROP TABLE IF EXISTS pets;"
  },
  {
    "path": "database/ql/examples/migrations/44_alter_table.up.sql",
    "content": "ALTER TABLE pets ADD predator bool;;"
  },
  {
    "path": "database/ql/ql.go",
    "content": "package ql\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t_ \"modernc.org/ql/driver\"\n)\n\nfunc init() {\n\tdatabase.Register(\"ql\", &Ql{})\n}\n\nvar DefaultMigrationsTable = \"schema_migrations\"\nvar (\n\tErrDatabaseDirty  = fmt.Errorf(\"database is dirty\")\n\tErrNilConfig      = fmt.Errorf(\"no config\")\n\tErrNoDatabaseName = fmt.Errorf(\"no database name\")\n\tErrAppendPEM      = fmt.Errorf(\"failed to append PEM\")\n)\n\ntype Config struct {\n\tMigrationsTable string\n\tDatabaseName    string\n}\n\ntype Ql struct {\n\tdb       *sql.DB\n\tisLocked atomic.Bool\n\n\tconfig *Config\n}\n\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif err := instance.Ping(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tmx := &Ql{\n\t\tdb:     instance,\n\t\tconfig: config,\n\t}\n\tif err := mx.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn mx, nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database, which deviates from the usual\n// convention of \"caller locks\" in the Ql type.\nfunc (m *Ql) ensureVersionTable() (err error) {\n\tif err = m.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := m.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\ttx, err := m.db.Begin()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif _, err := tx.Exec(fmt.Sprintf(`\n\tCREATE TABLE IF NOT EXISTS %s (version uint64, dirty bool);\n\tCREATE UNIQUE INDEX IF NOT EXISTS version_unique ON %s (version);\n`, m.config.MigrationsTable, m.config.MigrationsTable)); err != nil {\n\t\tif err := tx.Rollback(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn err\n\t}\n\tif err := tx.Commit(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (m *Ql) Open(url string) (database.Driver, error) {\n\tpurl, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdbfile := strings.Replace(migrate.FilterCustomQuery(purl).String(), \"ql://\", \"\", 1)\n\tdb, err := sql.Open(\"ql\", dbfile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmigrationsTable := purl.Query().Get(\"x-migrations-table\")\n\tif len(migrationsTable) == 0 {\n\t\tmigrationsTable = DefaultMigrationsTable\n\t}\n\tmx, err := WithInstance(db, &Config{\n\t\tDatabaseName:    purl.Path,\n\t\tMigrationsTable: migrationsTable,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn mx, nil\n}\nfunc (m *Ql) Close() error {\n\treturn m.db.Close()\n}\nfunc (m *Ql) Drop() (err error) {\n\tquery := `SELECT Name FROM __Table`\n\ttables, err := m.db.Query(query)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tdefer func() {\n\t\tif errClose := tables.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\ttableNames := make([]string, 0)\n\tfor tables.Next() {\n\t\tvar tableName string\n\t\tif err := tables.Scan(&tableName); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(tableName) > 0 {\n\t\t\tif !strings.HasPrefix(tableName, \"__\") {\n\t\t\t\ttableNames = append(tableNames, tableName)\n\t\t\t}\n\t\t}\n\t}\n\tif err := tables.Err(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tif len(tableNames) > 0 {\n\t\tfor _, t := range tableNames {\n\t\t\tquery := \"DROP TABLE \" + t\n\t\t\terr = m.executeQuery(query)\n\t\t\tif err != nil {\n\t\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\nfunc (m *Ql) Lock() error {\n\tif !m.isLocked.CompareAndSwap(false, true) {\n\t\treturn database.ErrLocked\n\t}\n\treturn nil\n}\nfunc (m *Ql) Unlock() error {\n\tif !m.isLocked.CompareAndSwap(true, false) {\n\t\treturn database.ErrNotLocked\n\t}\n\treturn nil\n}\nfunc (m *Ql) Run(migration io.Reader) error {\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\tquery := string(migr[:])\n\n\treturn m.executeQuery(query)\n}\nfunc (m *Ql) executeQuery(query string) error {\n\ttx, err := m.db.Begin()\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\tif _, err := tx.Exec(query); err != nil {\n\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\terr = errors.Join(err, errRollback)\n\t\t}\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tif err := tx.Commit(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction commit failed\"}\n\t}\n\treturn nil\n}\nfunc (m *Ql) SetVersion(version int, dirty bool) error {\n\ttx, err := m.db.Begin()\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\n\tquery := \"TRUNCATE TABLE \" + m.config.MigrationsTable\n\tif _, err := tx.Exec(query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\t// Also re-write the schema version for nil dirty versions to prevent\n\t// empty schema version for failed down migration on the first migration\n\t// See: https://github.com/golang-migrate/migrate/issues/330\n\tif version >= 0 || (version == database.NilVersion && dirty) {\n\t\tquery := fmt.Sprintf(`INSERT INTO %s (version, dirty) VALUES (uint64(?1), ?2)`,\n\t\t\tm.config.MigrationsTable)\n\t\tif _, err := tx.Exec(query, version, dirty); err != nil {\n\t\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\t\terr = errors.Join(err, errRollback)\n\t\t\t}\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction commit failed\"}\n\t}\n\n\treturn nil\n}\n\nfunc (m *Ql) Version() (version int, dirty bool, err error) {\n\tquery := \"SELECT version, dirty FROM \" + m.config.MigrationsTable + \" LIMIT 1\"\n\terr = m.db.QueryRow(query).Scan(&version, &dirty)\n\tif err != nil {\n\t\treturn database.NilVersion, false, nil\n\t}\n\treturn version, dirty, nil\n}\n"
  },
  {
    "path": "database/ql/ql_test.go",
    "content": "package ql\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n\t_ \"modernc.org/ql/driver\"\n)\n\nfunc Test(t *testing.T) {\n\tdir := t.TempDir()\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"ql.db\"))\n\tp := &Ql{}\n\taddr := fmt.Sprintf(\"ql://%s\", filepath.Join(dir, \"ql.db\"))\n\td, err := p.Open(addr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdb, err := sql.Open(\"ql\", filepath.Join(dir, \"ql.db\"))\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\tdt.Test(t, d, []byte(\"CREATE TABLE t (Qty int, Name string);\"))\n}\n\nfunc TestMigrate(t *testing.T) {\n\tdir := t.TempDir()\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"ql.db\"))\n\n\tdb, err := sql.Open(\"ql\", filepath.Join(dir, \"ql.db\"))\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\n\tdriver, err := WithInstance(db, &Config{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tm, err := migrate.NewWithDatabaseInstance(\n\t\t\"file://./examples/migrations\",\n\t\t\"ql\", driver)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdt.TestMigrate(t, m)\n}\n"
  },
  {
    "path": "database/redshift/README.md",
    "content": "# Redshift\n\n`redshift://user:password@host:port/dbname?query`\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |\n| `dbname` | `DatabaseName` | The name of the database to connect to |\n| `search_path` | | This variable specifies the order in which schemas are searched when an object is referenced by a simple name with no schema specified. |\n| `user` | | The user to sign in as |\n| `password` | | The user's password | \n| `host` | | The host to connect to. Values that start with / are for unix domain sockets. (default is localhost) |\n| `port` | | The port to bind to. (default is 5439) |\n| `fallback_application_name` | | An application_name to fall back to if one isn't provided. |\n| `connect_timeout` | | Maximum wait for connection, in seconds. Zero or not specified means wait indefinitely. |\n| `sslcert` | | Cert file location. The file must contain PEM encoded data. |\n| `sslkey` | | Key file location. The file must contain PEM encoded data. |\n| `sslrootcert` | | The location of the root certificate file. The file must contain PEM encoded data. | \n| `sslmode` | | Whether or not to use SSL (disable\\|require\\|verify-ca\\|verify-full) |\n\nRedshift is PostgreSQL compatible but has some specific features (or lack thereof) that require slightly different behavior.\n"
  },
  {
    "path": "database/redshift/examples/migrations/1085649617_create_users_table.down.sql",
    "content": "DROP TABLE IF EXISTS users;\n"
  },
  {
    "path": "database/redshift/examples/migrations/1085649617_create_users_table.up.sql",
    "content": "CREATE TABLE users (\n  user_id integer unique,\n  name    varchar(40),\n  email   varchar(40)\n);\n"
  },
  {
    "path": "database/redshift/examples/migrations/1185749658_add_city_to_users.down.sql",
    "content": "ALTER TABLE users DROP COLUMN IF EXISTS city;\n"
  },
  {
    "path": "database/redshift/examples/migrations/1185749658_add_city_to_users.up.sql",
    "content": "ALTER TABLE users ADD COLUMN city varchar(100);\n\n\n"
  },
  {
    "path": "database/redshift/examples/migrations/1285849751_add_index_on_user_emails.down.sql",
    "content": "DROP INDEX IF EXISTS users_email_index;\n"
  },
  {
    "path": "database/redshift/examples/migrations/1285849751_add_index_on_user_emails.up.sql",
    "content": "CREATE UNIQUE INDEX CONCURRENTLY users_email_index ON users (email);\n\n-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/redshift/examples/migrations/1385949617_create_books_table.down.sql",
    "content": "DROP TABLE IF EXISTS books;\n"
  },
  {
    "path": "database/redshift/examples/migrations/1385949617_create_books_table.up.sql",
    "content": "CREATE TABLE books (\n  user_id integer,\n  name    varchar(40),\n  author  varchar(40)\n);\n"
  },
  {
    "path": "database/redshift/examples/migrations/1485949617_create_movies_table.down.sql",
    "content": "DROP TABLE IF EXISTS movies;\n"
  },
  {
    "path": "database/redshift/examples/migrations/1485949617_create_movies_table.up.sql",
    "content": "CREATE TABLE movies (\n  user_id   integer,\n  name      varchar(40),\n  director  varchar(40)\n);\n"
  },
  {
    "path": "database/redshift/examples/migrations/1585849751_just_a_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/redshift/examples/migrations/1685849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/redshift/examples/migrations/1785849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/redshift/examples/migrations/1885849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/redshift/redshift.go",
    "content": "//go:build go1.9\n\npackage redshift\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t\"github.com/lib/pq\"\n)\n\nfunc init() {\n\tdb := Redshift{}\n\tdatabase.Register(\"redshift\", &db)\n}\n\nvar DefaultMigrationsTable = \"schema_migrations\"\n\nvar (\n\tErrNilConfig      = fmt.Errorf(\"no config\")\n\tErrNoDatabaseName = fmt.Errorf(\"no database name\")\n)\n\ntype Config struct {\n\tMigrationsTable string\n\tDatabaseName    string\n}\n\ntype Redshift struct {\n\tisLocked atomic.Bool\n\tconn     *sql.Conn\n\tdb       *sql.DB\n\n\t// Open and WithInstance need to guarantee that config is never nil\n\tconfig *Config\n}\n\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif err := instance.Ping(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif config.DatabaseName == \"\" {\n\t\tquery := `SELECT CURRENT_DATABASE()`\n\t\tvar databaseName string\n\t\tif err := instance.QueryRow(query).Scan(&databaseName); err != nil {\n\t\t\treturn nil, &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\tif len(databaseName) == 0 {\n\t\t\treturn nil, ErrNoDatabaseName\n\t\t}\n\n\t\tconfig.DatabaseName = databaseName\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tconn, err := instance.Conn(context.Background())\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpx := &Redshift{\n\t\tconn:   conn,\n\t\tdb:     instance,\n\t\tconfig: config,\n\t}\n\n\tif err := px.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\nfunc (p *Redshift) Open(url string) (database.Driver, error) {\n\tpurl, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpurl.Scheme = \"postgres\"\n\n\tdb, err := sql.Open(\"postgres\", migrate.FilterCustomQuery(purl).String())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmigrationsTable := purl.Query().Get(\"x-migrations-table\")\n\n\tpx, err := WithInstance(db, &Config{\n\t\tDatabaseName:    purl.Path,\n\t\tMigrationsTable: migrationsTable,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\nfunc (p *Redshift) Close() error {\n\tconnErr := p.conn.Close()\n\tdbErr := p.db.Close()\n\tif connErr != nil || dbErr != nil {\n\t\treturn fmt.Errorf(\"conn: %v, db: %v\", connErr, dbErr)\n\t}\n\treturn nil\n}\n\n// Redshift does not support advisory lock functions: https://docs.aws.amazon.com/redshift/latest/dg/c_unsupported-postgresql-functions.html\nfunc (p *Redshift) Lock() error {\n\tif !p.isLocked.CompareAndSwap(false, true) {\n\t\treturn database.ErrLocked\n\t}\n\treturn nil\n}\n\nfunc (p *Redshift) Unlock() error {\n\tif !p.isLocked.CompareAndSwap(true, false) {\n\t\treturn database.ErrNotLocked\n\t}\n\treturn nil\n}\n\nfunc (p *Redshift) Run(migration io.Reader) error {\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// run migration\n\tquery := string(migr[:])\n\tif _, err := p.conn.ExecContext(context.Background(), query); err != nil {\n\t\tif pgErr, ok := err.(*pq.Error); ok {\n\t\t\tvar line uint\n\t\t\tvar col uint\n\t\t\tvar lineColOK bool\n\t\t\tif pgErr.Position != \"\" {\n\t\t\t\tif pos, err := strconv.ParseUint(pgErr.Position, 10, 64); err == nil {\n\t\t\t\t\tline, col, lineColOK = computeLineFromPos(query, int(pos))\n\t\t\t\t}\n\t\t\t}\n\t\t\tmessage := fmt.Sprintf(\"migration failed: %s\", pgErr.Message)\n\t\t\tif lineColOK {\n\t\t\t\tmessage = fmt.Sprintf(\"%s (column %d)\", message, col)\n\t\t\t}\n\t\t\tif pgErr.Detail != \"\" {\n\t\t\t\tmessage = fmt.Sprintf(\"%s, %s\", message, pgErr.Detail)\n\t\t\t}\n\t\t\treturn database.Error{OrigErr: err, Err: message, Query: migr, Line: line}\n\t\t}\n\t\treturn database.Error{OrigErr: err, Err: \"migration failed\", Query: migr}\n\t}\n\n\treturn nil\n}\n\nfunc computeLineFromPos(s string, pos int) (line uint, col uint, ok bool) {\n\t// replace crlf with lf\n\ts = strings.ReplaceAll(s, \"\\r\\n\", \"\\n\")\n\t// pg docs: pos uses index 1 for the first character, and positions are measured in characters not bytes\n\trunes := []rune(s)\n\tif pos > len(runes) {\n\t\treturn 0, 0, false\n\t}\n\tsel := runes[:pos]\n\tline = uint(runesCount(sel, newLine) + 1)\n\tcol = uint(pos - 1 - runesLastIndex(sel, newLine))\n\treturn line, col, true\n}\n\nconst newLine = '\\n'\n\nfunc runesCount(input []rune, target rune) int {\n\tvar count int\n\tfor _, r := range input {\n\t\tif r == target {\n\t\t\tcount++\n\t\t}\n\t}\n\treturn count\n}\n\nfunc runesLastIndex(input []rune, target rune) int {\n\tfor i := len(input) - 1; i >= 0; i-- {\n\t\tif input[i] == target {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (p *Redshift) SetVersion(version int, dirty bool) error {\n\ttx, err := p.conn.BeginTx(context.Background(), &sql.TxOptions{})\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\n\tquery := `DELETE FROM \"` + p.config.MigrationsTable + `\"`\n\tif _, err := tx.Exec(query); err != nil {\n\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\terr = errors.Join(err, errRollback)\n\t\t}\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\t// Also re-write the schema version for nil dirty versions to prevent\n\t// empty schema version for failed down migration on the first migration\n\t// See: https://github.com/golang-migrate/migrate/issues/330\n\tif version >= 0 || (version == database.NilVersion && dirty) {\n\t\tquery = `INSERT INTO \"` + p.config.MigrationsTable + `\" (version, dirty) VALUES ($1, $2)`\n\t\tif _, err := tx.Exec(query, version, dirty); err != nil {\n\t\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\t\terr = errors.Join(err, errRollback)\n\t\t\t}\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction commit failed\"}\n\t}\n\n\treturn nil\n}\n\nfunc (p *Redshift) Version() (version int, dirty bool, err error) {\n\tquery := `SELECT version, dirty FROM \"` + p.config.MigrationsTable + `\" LIMIT 1`\n\terr = p.conn.QueryRowContext(context.Background(), query).Scan(&version, &dirty)\n\tswitch {\n\tcase err == sql.ErrNoRows:\n\t\treturn database.NilVersion, false, nil\n\n\tcase err != nil:\n\t\tif e, ok := err.(*pq.Error); ok {\n\t\t\tif e.Code.Name() == \"undefined_table\" {\n\t\t\t\treturn database.NilVersion, false, nil\n\t\t\t}\n\t\t}\n\t\treturn 0, false, &database.Error{OrigErr: err, Query: []byte(query)}\n\n\tdefault:\n\t\treturn version, dirty, nil\n\t}\n}\n\nfunc (p *Redshift) Drop() (err error) {\n\t// select all tables in current schema\n\tquery := `SELECT table_name FROM information_schema.tables WHERE table_schema=(SELECT current_schema()) AND table_type='BASE TABLE'`\n\ttables, err := p.conn.QueryContext(context.Background(), query)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tdefer func() {\n\t\tif errClose := tables.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\t// delete one table after another\n\ttableNames := make([]string, 0)\n\tfor tables.Next() {\n\t\tvar tableName string\n\t\tif err := tables.Scan(&tableName); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(tableName) > 0 {\n\t\t\ttableNames = append(tableNames, tableName)\n\t\t}\n\t}\n\tif err := tables.Err(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tif len(tableNames) > 0 {\n\t\t// delete one by one ...\n\t\tfor _, t := range tableNames {\n\t\t\tquery = `DROP TABLE IF EXISTS ` + t + ` CASCADE`\n\t\t\tif _, err := p.conn.ExecContext(context.Background(), query); err != nil {\n\t\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database, which deviates from the usual\n// convention of \"caller locks\" in the Redshift type.\nfunc (p *Redshift) ensureVersionTable() (err error) {\n\tif err = p.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := p.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\t// check if migration table exists\n\tvar count int\n\tquery := `SELECT COUNT(1) FROM information_schema.tables WHERE table_name = $1 AND table_schema = (SELECT current_schema()) LIMIT 1`\n\tif err := p.conn.QueryRowContext(context.Background(), query, p.config.MigrationsTable).Scan(&count); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tif count == 1 {\n\t\treturn nil\n\t}\n\n\t// if not, create the empty migration table\n\tquery = `CREATE TABLE \"` + p.config.MigrationsTable + `\" (version bigint not null primary key, dirty boolean not null)`\n\tif _, err := p.conn.ExecContext(context.Background(), query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "database/redshift/redshift_test.go",
    "content": "package redshift\n\n// error codes https://github.com/lib/pq/blob/master/error.go\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"database/sql\"\n\tsqldriver \"database/sql/driver\"\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n)\n\nimport (\n\t\"github.com/dhui/dktest\"\n)\n\nimport (\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t\"github.com/golang-migrate/migrate/v4/dktesting\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nconst (\n\tpgPassword = \"redshift\"\n)\n\nvar (\n\topts = dktest.Options{\n\t\tEnv:          map[string]string{\"POSTGRES_PASSWORD\": pgPassword},\n\t\tPortRequired: true,\n\t\tReadyFunc:    isReady,\n\t}\n\n\tspecs = []dktesting.ContainerSpec{\n\t\t{ImageName: \"migrate/postgres8:8\", Options: opts},\n\t}\n)\n\nfunc redshiftConnectionString(host, port string) string {\n\treturn connectionString(\"redshift\", host, port)\n}\n\nfunc pgConnectionString(host, port string) string {\n\treturn connectionString(\"postgres\", host, port)\n}\n\nfunc connectionString(schema, host, port string) string {\n\treturn fmt.Sprintf(\"%s://postgres:%s@%s:%s/postgres?sslmode=disable\", schema, pgPassword, host, port)\n}\n\nfunc isReady(ctx context.Context, c dktest.ContainerInfo) bool {\n\tip, port, err := c.FirstPort()\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tdb, err := sql.Open(\"postgres\", pgConnectionString(ip, port))\n\tif err != nil {\n\t\treturn false\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\tlog.Println(\"close error:\", err)\n\t\t}\n\t}()\n\tif err = db.PingContext(ctx); err != nil {\n\t\tswitch err {\n\t\tcase sqldriver.ErrBadConn, io.EOF:\n\t\t\treturn false\n\t\tdefault:\n\t\t\tlog.Println(err)\n\t\t}\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc Test(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := redshiftConnectionString(ip, port)\n\t\tp := &Redshift{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\t})\n}\n\nfunc TestMigrate(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := redshiftConnectionString(ip, port)\n\t\tp := &Redshift{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://./examples/migrations\", \"postgres\", d)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\t})\n}\n\nfunc TestMultiStatement(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := redshiftConnectionString(ip, port)\n\t\tp := &Redshift{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tif err := d.Run(bytes.NewReader([]byte(\"CREATE TABLE foo (foo text); CREATE TABLE bar (bar text);\"))); err != nil {\n\t\t\tt.Fatalf(\"expected err to be nil, got %v\", err)\n\t\t}\n\n\t\t// make sure second table exists\n\t\tvar exists bool\n\t\tif err := d.(*Redshift).conn.QueryRowContext(context.Background(), \"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'bar' AND table_schema = (SELECT current_schema()))\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Fatalf(\"expected table bar to exist\")\n\t\t}\n\t})\n}\n\nfunc TestErrorParsing(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := redshiftConnectionString(ip, port)\n\t\tp := &Redshift{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\twantErr := `migration failed: syntax error at or near \"TABLEE\" (column 37) in line 1: CREATE TABLE foo ` +\n\t\t\t`(foo text); CREATE TABLEE bar (bar text); (details: pq: syntax error at or near \"TABLEE\")`\n\t\tif err := d.Run(bytes.NewReader([]byte(\"CREATE TABLE foo (foo text); CREATE TABLEE bar (bar text);\"))); err == nil {\n\t\t\tt.Fatal(\"expected err but got nil\")\n\t\t} else if err.Error() != wantErr {\n\t\t\tt.Fatalf(\"expected '%s' but got '%s'\", wantErr, err.Error())\n\t\t}\n\t})\n}\n\nfunc TestFilterCustomQuery(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-custom=foobar\", pgPassword, ip, port)\n\t\tp := &Redshift{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t})\n}\n\nfunc TestWithSchema(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := redshiftConnectionString(ip, port)\n\t\tp := &Redshift{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\t// create foobar schema\n\t\tif err := d.Run(bytes.NewReader([]byte(\"CREATE SCHEMA foobar AUTHORIZATION postgres\"))); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := d.SetVersion(1, false); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// re-connect using that schema\n\t\td2, err := p.Open(fmt.Sprintf(\"postgres://postgres:%s@%v:%v/postgres?sslmode=disable&search_path=foobar\", pgPassword, ip, port))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d2.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tversion, _, err := d2.Version()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif version != database.NilVersion {\n\t\t\tt.Fatal(\"expected NilVersion\")\n\t\t}\n\n\t\t// now update version and compare\n\t\tif err := d2.SetVersion(2, false); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tversion, _, err = d2.Version()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif version != 2 {\n\t\t\tt.Fatal(\"expected version 2\")\n\t\t}\n\n\t\t// meanwhile, the public schema still has the other version\n\t\tversion, _, err = d.Version()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif version != 1 {\n\t\t\tt.Fatal(\"expected version 2\")\n\t\t}\n\t})\n}\n\nfunc TestWithInstance(t *testing.T) {\n\n}\n\nfunc TestRedshift_Lock(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.FirstPort()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := pgConnectionString(ip, port)\n\t\tp := &Redshift{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\n\t\tps := d.(*Redshift)\n\n\t\terr = ps.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = ps.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = ps.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = ps.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc Test_computeLineFromPos(t *testing.T) {\n\ttestcases := []struct {\n\t\tpos      int\n\t\twantLine uint\n\t\twantCol  uint\n\t\tinput    string\n\t\twantOk   bool\n\t}{\n\t\t{\n\t\t\t15, 2, 6, \"SELECT *\\nFROM foo\", true, // foo table does not exists\n\t\t},\n\t\t{\n\t\t\t16, 3, 6, \"SELECT *\\n\\nFROM foo\", true, // foo table does not exists, empty line\n\t\t},\n\t\t{\n\t\t\t25, 3, 7, \"SELECT *\\nFROM foo\\nWHERE x\", true, // x column error\n\t\t},\n\t\t{\n\t\t\t27, 5, 7, \"SELECT *\\n\\nFROM foo\\n\\nWHERE x\", true, // x column error, empty lines\n\t\t},\n\t\t{\n\t\t\t10, 2, 1, \"SELECT *\\nFROMM foo\", true, // FROMM typo\n\t\t},\n\t\t{\n\t\t\t11, 3, 1, \"SELECT *\\n\\nFROMM foo\", true, // FROMM typo, empty line\n\t\t},\n\t\t{\n\t\t\t17, 2, 8, \"SELECT *\\nFROM foo\", true, // last character\n\t\t},\n\t\t{\n\t\t\t18, 0, 0, \"SELECT *\\nFROM foo\", false, // invalid position\n\t\t},\n\t}\n\tfor i, tc := range testcases {\n\t\tt.Run(\"tc\"+strconv.Itoa(i), func(t *testing.T) {\n\t\t\trun := func(crlf bool, nonASCII bool) {\n\t\t\t\tvar name string\n\t\t\t\tif crlf {\n\t\t\t\t\tname = \"crlf\"\n\t\t\t\t} else {\n\t\t\t\t\tname = \"lf\"\n\t\t\t\t}\n\t\t\t\tif nonASCII {\n\t\t\t\t\tname += \"-nonascii\"\n\t\t\t\t} else {\n\t\t\t\t\tname += \"-ascii\"\n\t\t\t\t}\n\t\t\t\tt.Run(name, func(t *testing.T) {\n\t\t\t\t\tinput := tc.input\n\t\t\t\t\tif crlf {\n\t\t\t\t\t\tinput = strings.ReplaceAll(input, \"\\n\", \"\\r\\n\")\n\t\t\t\t\t}\n\t\t\t\t\tif nonASCII {\n\t\t\t\t\t\tinput = strings.ReplaceAll(input, \"FROM\", \"FRÖM\")\n\t\t\t\t\t}\n\t\t\t\t\tgotLine, gotCol, gotOK := computeLineFromPos(input, tc.pos)\n\n\t\t\t\t\tif tc.wantOk {\n\t\t\t\t\t\tt.Logf(\"pos %d, want %d:%d, %#v\", tc.pos, tc.wantLine, tc.wantCol, input)\n\t\t\t\t\t}\n\n\t\t\t\t\tif gotOK != tc.wantOk {\n\t\t\t\t\t\tt.Fatalf(\"expected ok %v but got %v\", tc.wantOk, gotOK)\n\t\t\t\t\t}\n\t\t\t\t\tif gotLine != tc.wantLine {\n\t\t\t\t\t\tt.Fatalf(\"expected line %d but got %d\", tc.wantLine, gotLine)\n\t\t\t\t\t}\n\t\t\t\t\tif gotCol != tc.wantCol {\n\t\t\t\t\t\tt.Fatalf(\"expected col %d but got %d\", tc.wantCol, gotCol)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t\trun(false, false)\n\t\t\trun(true, false)\n\t\t\trun(false, true)\n\t\t\trun(true, true)\n\t\t})\n\t}\n\n}\n"
  },
  {
    "path": "database/rqlite/README.md",
    "content": "# rqlite\n\n`rqlite://admin:secret@server1.example.com:4001/?level=strong&timeout=5`\n\nThe `rqlite` url scheme is used for both secure and insecure connections. If connecting to an insecure database, pass `x-connect-insecure` in your URL query, or use `WithInstance` to pass an established connection.\n\nThe migrations table name is configurable through the `x-migrations-table` URL query parameter, or by using `WithInstance` and passing `MigrationsTable` through `Config`.\n\nOther connect parameters are directly passed through to the database driver. For examples of connection strings, see https://github.com/rqlite/gorqlite#examples.\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| `x-connect-insecure` | n/a: set on instance | Boolean to indicate whether to use an insecure connection. Defaults to `false`. |\n| `x-migrations-table` | `MigrationsTable` | Name of the migrations table.  Defaults to `schema_migrations`. |\n\n## Notes\n\n* Uses the https://github.com/rqlite/gorqlite driver\n"
  },
  {
    "path": "database/rqlite/examples/migrations/33_create_table.down.sql",
    "content": "DROP TABLE IF EXISTS pets;"
  },
  {
    "path": "database/rqlite/examples/migrations/33_create_table.up.sql",
    "content": "CREATE TABLE pets (\n  name string\n);"
  },
  {
    "path": "database/rqlite/examples/migrations/44_alter_table.down.sql",
    "content": "DROP TABLE IF EXISTS pets;"
  },
  {
    "path": "database/rqlite/examples/migrations/44_alter_table.up.sql",
    "content": "ALTER TABLE pets ADD predator bool;\n"
  },
  {
    "path": "database/rqlite/rqlite.go",
    "content": "package rqlite\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t\"github.com/rqlite/gorqlite\"\n)\n\nfunc init() {\n\tdatabase.Register(\"rqlite\", &Rqlite{})\n}\n\nconst (\n\t// DefaultMigrationsTable defines the default rqlite migrations table\n\tDefaultMigrationsTable = \"schema_migrations\"\n\n\t// DefaultConnectInsecure defines the default setting for connect insecure\n\tDefaultConnectInsecure = false\n)\n\n// ErrNilConfig is returned if no configuration was passed to WithInstance\nvar ErrNilConfig = fmt.Errorf(\"no config\")\n\n// ErrBadConfig is returned if configuration was invalid\nvar ErrBadConfig = fmt.Errorf(\"bad parameter\")\n\n// Config defines the driver configuration\ntype Config struct {\n\t// ConnectInsecure sets whether the connection uses TLS. Ineffectual when using WithInstance\n\tConnectInsecure bool\n\t// MigrationsTable configures the migrations table name\n\tMigrationsTable string\n}\n\ntype Rqlite struct {\n\tdb       *gorqlite.Connection\n\tisLocked atomic.Bool\n\n\tconfig *Config\n}\n\n// WithInstance creates a rqlite database driver with an existing gorqlite database connection\n// and a Config struct\nfunc WithInstance(instance *gorqlite.Connection, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\t// we use the consistency level check as a database ping\n\tif _, err := instance.ConsistencyLevel(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tdriver := &Rqlite{\n\t\tdb:     instance,\n\t\tconfig: config,\n\t}\n\n\tif err := driver.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn driver, nil\n}\n\n// OpenURL creates a rqlite database driver from a connect URL\nfunc OpenURL(url string) (database.Driver, error) {\n\td := &Rqlite{}\n\treturn d.Open(url)\n}\n\nfunc (r *Rqlite) ensureVersionTable() (err error) {\n\tif err = r.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := r.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\tstmts := []string{\n\t\tfmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s (version uint64, dirty bool)`, r.config.MigrationsTable),\n\t\tfmt.Sprintf(`CREATE UNIQUE INDEX IF NOT EXISTS version_unique ON %s (version)`, r.config.MigrationsTable),\n\t}\n\n\tif _, err := r.db.Write(stmts); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Open returns a new driver instance configured with parameters\n// coming from the URL string. Migrate will call this function\n// only once per instance.\nfunc (r *Rqlite) Open(url string) (database.Driver, error) {\n\tdburl, config, err := parseUrl(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tr.config = config\n\n\tr.db, err = gorqlite.Open(dburl.String())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := r.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r, nil\n}\n\n// Close closes the underlying database instance managed by the driver.\n// Migrate will call this function only once per instance.\nfunc (r *Rqlite) Close() error {\n\tr.db.Close()\n\treturn nil\n}\n\n// Lock should acquire a database lock so that only one migration process\n// can run at a time. Migrate will call this function before Run is called.\n// If the implementation can't provide this functionality, return nil.\n// Return database.ErrLocked if database is already locked.\nfunc (r *Rqlite) Lock() error {\n\tif !r.isLocked.CompareAndSwap(false, true) {\n\t\treturn database.ErrLocked\n\t}\n\treturn nil\n}\n\n// Unlock should release the lock. Migrate will call this function after\n// all migrations have been run.\nfunc (r *Rqlite) Unlock() error {\n\tif !r.isLocked.CompareAndSwap(true, false) {\n\t\treturn database.ErrNotLocked\n\t}\n\treturn nil\n}\n\n// Run applies a migration to the database. migration is guaranteed to be not nil.\nfunc (r *Rqlite) Run(migration io.Reader) error {\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tquery := string(migr[:])\n\tif _, err := r.db.WriteOne(query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn nil\n}\n\n// SetVersion saves version and dirty state.\n// Migrate will call this function before and after each call to Run.\n// version must be >= -1. -1 means NilVersion.\nfunc (r *Rqlite) SetVersion(version int, dirty bool) error {\n\tdeleteQuery := fmt.Sprintf(`DELETE FROM %s`, r.config.MigrationsTable)\n\tstatements := []gorqlite.ParameterizedStatement{\n\t\t{\n\t\t\tQuery: deleteQuery,\n\t\t},\n\t}\n\n\t// Also re-write the schema version for nil dirty versions to prevent\n\t// empty schema version for failed down migration on the first migration\n\t// See: https://github.com/golang-migrate/migrate/issues/330\n\tinsertQuery := fmt.Sprintf(`INSERT INTO %s (version, dirty) VALUES (?, ?)`, r.config.MigrationsTable)\n\tif version >= 0 || (version == database.NilVersion && dirty) {\n\t\tstatements = append(statements, gorqlite.ParameterizedStatement{\n\t\t\tQuery: insertQuery,\n\t\t\tArguments: []interface{}{\n\t\t\t\tversion,\n\t\t\t\tdirty,\n\t\t\t},\n\t\t})\n\t}\n\n\twr, err := r.db.WriteParameterized(statements)\n\tif err != nil {\n\t\tfor i, res := range wr {\n\t\t\tif res.Err != nil {\n\t\t\t\treturn &database.Error{OrigErr: err, Query: []byte(statements[i].Query)}\n\t\t\t}\n\t\t}\n\n\t\t// if somehow we're still here, return the original error with combined queries\n\t\treturn &database.Error{OrigErr: err, Query: []byte(deleteQuery + \"\\n\" + insertQuery)}\n\t}\n\n\treturn nil\n}\n\n// Version returns the currently active version and if the database is dirty.\n// When no migration has been applied, it must return version -1.\n// Dirty means, a previous migration failed and user interaction is required.\nfunc (r *Rqlite) Version() (version int, dirty bool, err error) {\n\tquery := \"SELECT version, dirty FROM \" + r.config.MigrationsTable + \" LIMIT 1\"\n\n\tqr, err := r.db.QueryOne(query)\n\tif err != nil {\n\t\treturn database.NilVersion, false, nil\n\t}\n\n\tif !qr.Next() {\n\t\treturn database.NilVersion, false, nil\n\t}\n\n\tif err := qr.Scan(&version, &dirty); err != nil {\n\t\treturn database.NilVersion, false, &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn version, dirty, nil\n}\n\n// Drop deletes everything in the database.\n// Note that this is a breaking action, a new call to Open() is necessary to\n// ensure subsequent calls work as expected.\nfunc (r *Rqlite) Drop() error {\n\tquery := `SELECT name FROM sqlite_master WHERE type = 'table'`\n\n\ttables, err := r.db.QueryOne(query)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tstatements := make([]string, 0)\n\tfor tables.Next() {\n\t\tvar tableName string\n\t\tif err := tables.Scan(&tableName); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif len(tableName) > 0 {\n\t\t\tstatement := fmt.Sprintf(`DROP TABLE %s`, tableName)\n\t\t\tstatements = append(statements, statement)\n\t\t}\n\t}\n\n\t// return if nothing to do\n\tif len(statements) <= 0 {\n\t\treturn nil\n\t}\n\n\twr, err := r.db.Write(statements)\n\tif err != nil {\n\t\tfor i, res := range wr {\n\t\t\tif res.Err != nil {\n\t\t\t\treturn &database.Error{OrigErr: err, Query: []byte(statements[i])}\n\t\t\t}\n\t\t}\n\n\t\t// if somehow we're still here, return the original error with combined queries\n\t\treturn &database.Error{OrigErr: err, Query: []byte(strings.Join(statements, \"\\n\"))}\n\t}\n\n\treturn nil\n}\n\nfunc parseUrl(url string) (*nurl.URL, *Config, error) {\n\tparsedUrl, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tconfig, err := parseConfigFromQuery(parsedUrl.Query())\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif parsedUrl.Scheme != \"rqlite\" {\n\t\treturn nil, nil, fmt.Errorf(\"bad scheme: %w\", ErrBadConfig)\n\t}\n\n\t// adapt from rqlite to http/https schemes\n\tif config.ConnectInsecure {\n\t\tparsedUrl.Scheme = \"http\"\n\t} else {\n\t\tparsedUrl.Scheme = \"https\"\n\t}\n\n\tfilteredUrl := migrate.FilterCustomQuery(parsedUrl)\n\n\treturn filteredUrl, config, nil\n}\n\nfunc parseConfigFromQuery(queryVals nurl.Values) (*Config, error) {\n\tc := Config{\n\t\tConnectInsecure: DefaultConnectInsecure,\n\t\tMigrationsTable: DefaultMigrationsTable,\n\t}\n\n\tmigrationsTable := queryVals.Get(\"x-migrations-table\")\n\tif migrationsTable != \"\" {\n\t\tif strings.HasPrefix(migrationsTable, \"sqlite_\") {\n\t\t\treturn nil, fmt.Errorf(\"invalid value for x-migrations-table: %w\", ErrBadConfig)\n\t\t}\n\t\tc.MigrationsTable = migrationsTable\n\t}\n\n\tconnectInsecureStr := queryVals.Get(\"x-connect-insecure\")\n\tif connectInsecureStr != \"\" {\n\t\tconnectInsecure, err := strconv.ParseBool(connectInsecureStr)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid value for x-connect-insecure: %w\", ErrBadConfig)\n\t\t}\n\t\tc.ConnectInsecure = connectInsecure\n\t}\n\n\treturn &c, nil\n}\n"
  },
  {
    "path": "database/rqlite/rqlite_test.go",
    "content": "package rqlite\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/dhui/dktest\"\n\t\"github.com/rqlite/gorqlite\"\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t\"github.com/golang-migrate/migrate/v4/dktesting\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nvar defaultPort uint16 = 4001\n\nvar opts = dktest.Options{\n\tEnv:          map[string]string{\"NODE_ID\": \"1\"},\n\tPortRequired: true,\n\tReadyFunc:    isReady,\n}\nvar specs = []dktesting.ContainerSpec{\n\t{ImageName: \"rqlite/rqlite:7.21.4\", Options: opts},\n\t{ImageName: \"rqlite/rqlite:8.0.6\", Options: opts},\n\t{ImageName: \"rqlite/rqlite:8.11.1\", Options: opts},\n\t{ImageName: \"rqlite/rqlite:8.12.3\", Options: opts},\n}\n\nfunc isReady(ctx context.Context, c dktest.ContainerInfo) bool {\n\tip, port, err := c.Port(defaultPort)\n\tif err != nil {\n\t\tfmt.Println(\"error getting port\")\n\t\treturn false\n\t}\n\n\tstatusString := fmt.Sprintf(\"http://%s:%s/status\", ip, port)\n\tfmt.Println(statusString)\n\n\tvar readyResp struct {\n\t\tStore struct {\n\t\t\tReady bool `json:\"ready\"`\n\t\t} `json:\"store\"`\n\t}\n\n\tresp, err := http.Get(statusString)\n\tif err != nil {\n\t\tfmt.Println(\"error getting status\")\n\t\treturn false\n\t}\n\n\tif resp.StatusCode != 200 {\n\t\tfmt.Println(\"statusCode != 200\")\n\t\treturn false\n\t}\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tfmt.Println(\"error reading body\")\n\t\treturn false\n\t}\n\n\tif err := json.Unmarshal(body, &readyResp); err != nil {\n\t\tfmt.Println(\"error unmarshaling body\")\n\t\treturn false\n\t}\n\n\treturn readyResp.Store.Ready\n}\n\nfunc Test(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tassert.NoError(t, err)\n\n\t\tconnectString := fmt.Sprintf(\"rqlite://%s:%s?level=strong&disableClusterDiscovery=true&x-connect-insecure=true\", ip, port)\n\t\tt.Logf(\"DB connect string : %s\\n\", connectString)\n\n\t\tr := &Rqlite{}\n\t\td, err := r.Open(connectString)\n\t\tassert.NoError(t, err)\n\n\t\tdt.Test(t, d, []byte(\"CREATE TABLE t (Qty int, Name string);\"))\n\t})\n}\n\nfunc TestMigrate(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tassert.NoError(t, err)\n\n\t\tconnectString := fmt.Sprintf(\"rqlite://%s:%s?level=strong&disableClusterDiscovery=true&x-connect-insecure=true\", ip, port)\n\t\tt.Logf(\"DB connect string : %s\\n\", connectString)\n\n\t\tdriver, err := OpenURL(connectString)\n\t\tassert.NoError(t, err)\n\t\tdefer func() {\n\t\t\tif err := driver.Close(); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\n\t\tm, err := migrate.NewWithDatabaseInstance(\n\t\t\t\"file://./examples/migrations\",\n\t\t\t\"ql\", driver)\n\t\tassert.NoError(t, err)\n\n\t\tdt.TestMigrate(t, m)\n\t})\n}\n\nfunc TestBadConnectInsecureParam(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tassert.NoError(t, err)\n\n\t\tconnectString := fmt.Sprintf(\"rqlite://%s:%s?x-connect-insecure=foo\", ip, port)\n\t\tt.Logf(\"DB connect string : %s\\n\", connectString)\n\n\t\t_, err = OpenURL(connectString)\n\t\tassert.ErrorIs(t, err, ErrBadConfig)\n\t})\n}\n\nfunc TestBadProtocol(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tassert.NoError(t, err)\n\n\t\tconnectString := fmt.Sprintf(\"postgres://%s:%s/database\", ip, port)\n\t\tt.Logf(\"DB connect string : %s\\n\", connectString)\n\n\t\t_, err = OpenURL(connectString)\n\t\tassert.ErrorIs(t, err, ErrBadConfig)\n\t})\n}\n\nfunc TestNoConfig(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tassert.NoError(t, err)\n\n\t\t// gorqlite expects http(s) schemes\n\t\tconnectString := fmt.Sprintf(\"http://%s:%s?level=strong&disableClusterDiscovery=true\", ip, port)\n\t\tt.Logf(\"DB connect string : %s\\n\", connectString)\n\t\tdb, err := gorqlite.Open(connectString)\n\t\tassert.NoError(t, err)\n\n\t\t_, err = WithInstance(db, nil)\n\t\tassert.ErrorIs(t, err, ErrNilConfig)\n\t})\n}\n\nfunc TestWithInstanceEmptyConfig(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tassert.NoError(t, err)\n\n\t\t// gorqlite expects http(s) schemes\n\t\tconnectString := fmt.Sprintf(\"http://%s:%s?level=strong&disableClusterDiscovery=true\", ip, port)\n\t\tt.Logf(\"DB connect string : %s\\n\", connectString)\n\t\tdb, err := gorqlite.Open(connectString)\n\t\tassert.NoError(t, err)\n\n\t\tdriver, err := WithInstance(db, &Config{})\n\t\tassert.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tif err := driver.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\n\t\tm, err := migrate.NewWithDatabaseInstance(\n\t\t\t\"file://./examples/migrations\",\n\t\t\t\"ql\", driver)\n\t\tassert.NoError(t, err)\n\n\t\tt.Log(\"UP\")\n\t\terr = m.Up()\n\t\tassert.NoError(t, err)\n\n\t\t_, err = db.QueryOne(fmt.Sprintf(\"SELECT * FROM %s\", DefaultMigrationsTable))\n\t\tassert.NoError(t, err)\n\n\t\tt.Log(\"DOWN\")\n\t\terr = m.Down()\n\t\tassert.NoError(t, err)\n\t})\n}\n\nfunc TestMigrationTable(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tip, port, err := c.Port(defaultPort)\n\t\tassert.NoError(t, err)\n\n\t\t// gorqlite expects http(s) schemes\n\t\tconnectString := fmt.Sprintf(\"http://%s:%s?level=strong&disableClusterDiscovery=true\", ip, port)\n\t\tt.Logf(\"DB connect string : %s\\n\", connectString)\n\t\tdb, err := gorqlite.Open(connectString)\n\t\tassert.NoError(t, err)\n\n\t\tconfig := Config{MigrationsTable: \"my_migration_table\"}\n\t\tdriver, err := WithInstance(db, &config)\n\t\tassert.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tif err := driver.Close(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\n\t\tm, err := migrate.NewWithDatabaseInstance(\n\t\t\t\"file://./examples/migrations\",\n\t\t\t\"ql\", driver)\n\t\tassert.NoError(t, err)\n\n\t\tt.Log(\"UP\")\n\t\terr = m.Up()\n\t\tassert.NoError(t, err)\n\n\t\t_, err = db.QueryOne(fmt.Sprintf(\"SELECT * FROM %s\", config.MigrationsTable))\n\t\tassert.NoError(t, err)\n\n\t\t_, err = db.WriteOne(`INSERT INTO pets (name, predator) VALUES (\"franklin\", true)`)\n\t\tassert.NoError(t, err)\n\n\t\tres, err := db.QueryOne(`SELECT name, predator FROM pets LIMIT 1`)\n\t\tassert.NoError(t, err)\n\n\t\t_ = res.Next()\n\n\t\t// make sure we can use the migrated table\n\t\tvar petName string\n\t\tvar petPredator int\n\t\terr = res.Scan(&petName, &petPredator)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, petName, \"franklin\")\n\t\tassert.Equal(t, petPredator, 1)\n\n\t\tt.Log(\"DOWN\")\n\t\terr = m.Down()\n\t\tassert.NoError(t, err)\n\n\t\t_, err = db.QueryOne(fmt.Sprintf(\"SELECT * FROM %s\", config.MigrationsTable))\n\t\tassert.NoError(t, err)\n\t})\n}\n\nfunc TestParseUrl(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tpassedUrl      string\n\t\texpectedUrl    string\n\t\texpectedConfig *Config\n\t\texpectedErr    string\n\t}{\n\t\t{\n\t\t\t\"defaults\",\n\t\t\t\"rqlite://localhost:4001\",\n\t\t\t\"https://localhost:4001\",\n\t\t\t&Config{ConnectInsecure: DefaultConnectInsecure, MigrationsTable: DefaultMigrationsTable},\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"configure migration table\",\n\t\t\t\"rqlite://localhost:4001?x-migrations-table=foo\",\n\t\t\t\"https://localhost:4001\",\n\t\t\t&Config{ConnectInsecure: DefaultConnectInsecure, MigrationsTable: \"foo\"},\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"configure connect insecure\",\n\t\t\t\"rqlite://localhost:4001?x-connect-insecure=true\",\n\t\t\t\"http://localhost:4001\",\n\t\t\t&Config{ConnectInsecure: true, MigrationsTable: DefaultMigrationsTable},\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"invalid migration table\",\n\t\t\t\"rqlite://localhost:4001?x-migrations-table=sqlite_bar\",\n\t\t\t\"\",\n\t\t\tnil,\n\t\t\t\"invalid value for x-migrations-table: bad parameter\",\n\t\t},\n\t\t{\n\t\t\t\"invalid connect insecure\",\n\t\t\t\"rqlite://localhost:4001?x-connect-insecure=baz\",\n\t\t\t\"\",\n\t\t\tnil,\n\t\t\t\"invalid value for x-connect-insecure: bad parameter\",\n\t\t},\n\t\t{\n\t\t\t\"invalid url\",\n\t\t\tstring([]byte{0x7f}),\n\t\t\t\"\",\n\t\t\tnil,\n\t\t\t\"parse \\\"\\\\x7f\\\": net/url: invalid control character in URL\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tactualUrl, actualConfig, actualErr := parseUrl(tt.passedUrl)\n\t\t\tif tt.expectedUrl != \"\" {\n\t\t\t\tassert.Equal(t, tt.expectedUrl, actualUrl.String())\n\t\t\t} else {\n\t\t\t\tassert.Nil(t, actualUrl)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tt.expectedConfig, actualConfig)\n\n\t\t\tif tt.expectedErr == \"\" {\n\t\t\t\tassert.NoError(t, actualErr)\n\t\t\t} else {\n\t\t\t\tassert.EqualError(t, actualErr, tt.expectedErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "database/shell/README.md",
    "content": ""
  },
  {
    "path": "database/snowflake/README.md",
    "content": "# Snowflake\n\n`snowflake://user:password@accountname/schema/dbname?query`\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |\n\nSnowflake is PostgreSQL compatible but has some specific features (or lack thereof) that require slightly different behavior.\n\n## Status\nThis driver is not officially supported as there are no tests for it.\n"
  },
  {
    "path": "database/snowflake/snowflake.go",
    "content": "package snowflake\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t\"github.com/lib/pq\"\n\tsf \"github.com/snowflakedb/gosnowflake\"\n)\n\nfunc init() {\n\tdb := Snowflake{}\n\tdatabase.Register(\"snowflake\", &db)\n}\n\nvar DefaultMigrationsTable = \"schema_migrations\"\n\nvar (\n\tErrNilConfig          = fmt.Errorf(\"no config\")\n\tErrNoDatabaseName     = fmt.Errorf(\"no database name\")\n\tErrNoPassword         = fmt.Errorf(\"no password\")\n\tErrNoSchema           = fmt.Errorf(\"no schema\")\n\tErrNoSchemaOrDatabase = fmt.Errorf(\"no schema/database name\")\n)\n\ntype Config struct {\n\tMigrationsTable string\n\tDatabaseName    string\n}\n\ntype Snowflake struct {\n\tisLocked atomic.Bool\n\tconn     *sql.Conn\n\tdb       *sql.DB\n\n\t// Open and WithInstance need to guarantee that config is never nil\n\tconfig *Config\n}\n\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif err := instance.Ping(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif config.DatabaseName == \"\" {\n\t\tquery := `SELECT CURRENT_DATABASE()`\n\t\tvar databaseName string\n\t\tif err := instance.QueryRow(query).Scan(&databaseName); err != nil {\n\t\t\treturn nil, &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\tif len(databaseName) == 0 {\n\t\t\treturn nil, ErrNoDatabaseName\n\t\t}\n\n\t\tconfig.DatabaseName = databaseName\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tconn, err := instance.Conn(context.Background())\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpx := &Snowflake{\n\t\tconn:   conn,\n\t\tdb:     instance,\n\t\tconfig: config,\n\t}\n\n\tif err := px.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\nfunc (p *Snowflake) Open(url string) (database.Driver, error) {\n\tpurl, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpassword, isPasswordSet := purl.User.Password()\n\tif !isPasswordSet {\n\t\treturn nil, ErrNoPassword\n\t}\n\n\tsplitPath := strings.Split(purl.Path, \"/\")\n\tif len(splitPath) < 3 {\n\t\treturn nil, ErrNoSchemaOrDatabase\n\t}\n\n\tdatabase := splitPath[2]\n\tif len(database) == 0 {\n\t\treturn nil, ErrNoDatabaseName\n\t}\n\n\tschema := splitPath[1]\n\tif len(schema) == 0 {\n\t\treturn nil, ErrNoSchema\n\t}\n\n\tcfg := &sf.Config{\n\t\tAccount:  purl.Host,\n\t\tUser:     purl.User.Username(),\n\t\tPassword: password,\n\t\tDatabase: database,\n\t\tSchema:   schema,\n\t}\n\n\tdsn, err := sf.DSN(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdb, err := sql.Open(\"snowflake\", dsn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmigrationsTable := purl.Query().Get(\"x-migrations-table\")\n\n\tpx, err := WithInstance(db, &Config{\n\t\tDatabaseName:    database,\n\t\tMigrationsTable: migrationsTable,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\nfunc (p *Snowflake) Close() error {\n\tconnErr := p.conn.Close()\n\tdbErr := p.db.Close()\n\tif connErr != nil || dbErr != nil {\n\t\treturn fmt.Errorf(\"conn: %v, db: %v\", connErr, dbErr)\n\t}\n\treturn nil\n}\n\nfunc (p *Snowflake) Lock() error {\n\tif !p.isLocked.CompareAndSwap(false, true) {\n\t\treturn database.ErrLocked\n\t}\n\treturn nil\n}\n\nfunc (p *Snowflake) Unlock() error {\n\tif !p.isLocked.CompareAndSwap(true, false) {\n\t\treturn database.ErrNotLocked\n\t}\n\treturn nil\n}\n\nfunc (p *Snowflake) Run(migration io.Reader) error {\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// run migration\n\tquery := string(migr[:])\n\tif _, err := p.conn.ExecContext(context.Background(), query); err != nil {\n\t\tif pgErr, ok := err.(*pq.Error); ok {\n\t\t\tvar line uint\n\t\t\tvar col uint\n\t\t\tvar lineColOK bool\n\t\t\tif pgErr.Position != \"\" {\n\t\t\t\tif pos, err := strconv.ParseUint(pgErr.Position, 10, 64); err == nil {\n\t\t\t\t\tline, col, lineColOK = computeLineFromPos(query, int(pos))\n\t\t\t\t}\n\t\t\t}\n\t\t\tmessage := fmt.Sprintf(\"migration failed: %s\", pgErr.Message)\n\t\t\tif lineColOK {\n\t\t\t\tmessage = fmt.Sprintf(\"%s (column %d)\", message, col)\n\t\t\t}\n\t\t\tif pgErr.Detail != \"\" {\n\t\t\t\tmessage = fmt.Sprintf(\"%s, %s\", message, pgErr.Detail)\n\t\t\t}\n\t\t\treturn database.Error{OrigErr: err, Err: message, Query: migr, Line: line}\n\t\t}\n\t\treturn database.Error{OrigErr: err, Err: \"migration failed\", Query: migr}\n\t}\n\n\treturn nil\n}\n\nfunc computeLineFromPos(s string, pos int) (line uint, col uint, ok bool) {\n\t// replace crlf with lf\n\ts = strings.ReplaceAll(s, \"\\r\\n\", \"\\n\")\n\t// pg docs: pos uses index 1 for the first character, and positions are measured in characters not bytes\n\trunes := []rune(s)\n\tif pos > len(runes) {\n\t\treturn 0, 0, false\n\t}\n\tsel := runes[:pos]\n\tline = uint(runesCount(sel, newLine) + 1)\n\tcol = uint(pos - 1 - runesLastIndex(sel, newLine))\n\treturn line, col, true\n}\n\nconst newLine = '\\n'\n\nfunc runesCount(input []rune, target rune) int {\n\tvar count int\n\tfor _, r := range input {\n\t\tif r == target {\n\t\t\tcount++\n\t\t}\n\t}\n\treturn count\n}\n\nfunc runesLastIndex(input []rune, target rune) int {\n\tfor i := len(input) - 1; i >= 0; i-- {\n\t\tif input[i] == target {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (p *Snowflake) SetVersion(version int, dirty bool) error {\n\ttx, err := p.conn.BeginTx(context.Background(), &sql.TxOptions{})\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\n\tquery := `DELETE FROM \"` + p.config.MigrationsTable + `\"`\n\tif _, err := tx.Exec(query); err != nil {\n\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\terr = errors.Join(err, errRollback)\n\t\t}\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\t// Also re-write the schema version for nil dirty versions to prevent\n\t// empty schema version for failed down migration on the first migration\n\t// See: https://github.com/golang-migrate/migrate/issues/330\n\tif version >= 0 || (version == database.NilVersion && dirty) {\n\t\tquery = `INSERT INTO \"` + p.config.MigrationsTable + `\" (version,\n\t\t\t\tdirty) VALUES (` + strconv.FormatInt(int64(version), 10) + `,\n\t\t\t\t` + strconv.FormatBool(dirty) + `)`\n\t\tif _, err := tx.Exec(query); err != nil {\n\t\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\t\terr = errors.Join(err, errRollback)\n\t\t\t}\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction commit failed\"}\n\t}\n\n\treturn nil\n}\n\nfunc (p *Snowflake) Version() (version int, dirty bool, err error) {\n\tquery := `SELECT version, dirty FROM \"` + p.config.MigrationsTable + `\" LIMIT 1`\n\terr = p.conn.QueryRowContext(context.Background(), query).Scan(&version, &dirty)\n\tswitch {\n\tcase err == sql.ErrNoRows:\n\t\treturn database.NilVersion, false, nil\n\n\tcase err != nil:\n\t\tif e, ok := err.(*pq.Error); ok {\n\t\t\tif e.Code.Name() == \"undefined_table\" {\n\t\t\t\treturn database.NilVersion, false, nil\n\t\t\t}\n\t\t}\n\t\treturn 0, false, &database.Error{OrigErr: err, Query: []byte(query)}\n\n\tdefault:\n\t\treturn version, dirty, nil\n\t}\n}\n\nfunc (p *Snowflake) Drop() (err error) {\n\t// select all tables in current schema\n\tquery := `SELECT table_name FROM information_schema.tables WHERE table_schema=(SELECT current_schema()) AND table_type='BASE TABLE'`\n\ttables, err := p.conn.QueryContext(context.Background(), query)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tdefer func() {\n\t\tif errClose := tables.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\t// delete one table after another\n\ttableNames := make([]string, 0)\n\tfor tables.Next() {\n\t\tvar tableName string\n\t\tif err := tables.Scan(&tableName); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(tableName) > 0 {\n\t\t\ttableNames = append(tableNames, tableName)\n\t\t}\n\t}\n\tif err := tables.Err(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tif len(tableNames) > 0 {\n\t\t// delete one by one ...\n\t\tfor _, t := range tableNames {\n\t\t\tquery = `DROP TABLE IF EXISTS ` + t + ` CASCADE`\n\t\t\tif _, err := p.conn.ExecContext(context.Background(), query); err != nil {\n\t\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database, which deviates from the usual\n// convention of \"caller locks\" in the Snowflake type.\nfunc (p *Snowflake) ensureVersionTable() (err error) {\n\tif err = p.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := p.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\t// check if migration table exists\n\tvar count int\n\tquery := `SELECT COUNT(1) FROM information_schema.tables WHERE table_name = $1 AND table_schema = (SELECT current_schema()) LIMIT 1`\n\tif err := p.conn.QueryRowContext(context.Background(), query, p.config.MigrationsTable).Scan(&count); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tif count == 1 {\n\t\treturn nil\n\t}\n\n\t// if not, create the empty migration table\n\tquery = `CREATE TABLE if not exists \"` + p.config.MigrationsTable + `\" (\n\t\t\tversion bigint not null primary key, dirty boolean not null)`\n\tif _, err := p.conn.ExecContext(context.Background(), query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "database/spanner/README.md",
    "content": "# Google Cloud Spanner\n\n## Usage\n\nSee [Google Spanner Documentation](https://cloud.google.com/spanner/docs) for\nmore details.\n\nThe DSN must be given in the following format.\n\n`spanner://projects/{projectId}/instances/{instanceId}/databases/{databaseName}?param=true`\n\nas described in [README.md#database-urls](../../README.md#database-urls)\n\n| Param | WithInstance Config | Description |\n| ----- | ------------------- | ----------- |\n| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |\n| `x-clean-statements` | `CleanStatements` | Whether to parse and clean DDL statements before running migration towards Spanner (Required for comments and multiple statements) |\n| `url` | `DatabaseName` | The full path to the Spanner database resource. If provided as part of `Config` it must not contain a scheme or query string to match the format `projects/{projectId}/instances/{instanceId}/databases/{databaseName}`|\n| `projectId` || The Google Cloud Platform project id\n| `instanceId` || The id of the instance running Spanner\n| `databaseName` || The name of the Spanner database\n\n> **Note:** Google Cloud Spanner migrations can take a considerable amount of \n> time. The migrations provided as part of the example take about 6 minutes to \n> run on a small instance.\n>\n> ```log\n> 1481574547/u create_users_table (21.354507597s)\n> 1496539702/u add_city_to_users (41.647359754s)\n> 1496601752/u add_index_on_user_emails (2m12.155787369s)\n> 1496602638/u create_books_table (2m30.77299181s)\n\n## DDL with comments\n\nAt the moment the GCP Spanner backed does not seem to allow for comments (See https://issuetracker.google.com/issues/159730604)\nso in order to be able to use migration with DDL containing comments `x-clean-statements` is required\n\n## Multiple statements\n\nIn order to be able to use more than 1 DDL statement in the same migration file, the file has to be parsed and therefore the `x-clean-statements` flag is required\n\n## Testing\n\nTo unit test the `spanner` driver, `SPANNER_DATABASE` needs to be set. You'll\nneed to sign-up to Google Cloud Platform (GCP) and have a running Spanner\ninstance since it is not possible to run Google Spanner outside GCP.\n"
  },
  {
    "path": "database/spanner/examples/migrations/1481574547_create_users_table.down.sql",
    "content": "DROP TABLE Users\n"
  },
  {
    "path": "database/spanner/examples/migrations/1481574547_create_users_table.up.sql",
    "content": "CREATE TABLE Users (\n  UserId  INT64,\n  Name    STRING(40),\n  Email   STRING(83)\n) PRIMARY KEY(UserId)"
  },
  {
    "path": "database/spanner/examples/migrations/1496539702_add_city_to_users.down.sql",
    "content": "ALTER TABLE Users DROP COLUMN city"
  },
  {
    "path": "database/spanner/examples/migrations/1496539702_add_city_to_users.up.sql",
    "content": "ALTER TABLE Users ADD COLUMN city STRING(100)"
  },
  {
    "path": "database/spanner/examples/migrations/1496601752_add_index_on_user_emails.down.sql",
    "content": "DROP INDEX UsersEmailIndex\n"
  },
  {
    "path": "database/spanner/examples/migrations/1496601752_add_index_on_user_emails.up.sql",
    "content": "CREATE UNIQUE INDEX UsersEmailIndex ON Users (Email)\n"
  },
  {
    "path": "database/spanner/examples/migrations/1496602638_create_books_table.down.sql",
    "content": "DROP TABLE Books"
  },
  {
    "path": "database/spanner/examples/migrations/1496602638_create_books_table.up.sql",
    "content": "CREATE TABLE Books (\n  UserId INT64,\n  Name    STRING(40),\n  Author  STRING(40)\n) PRIMARY KEY(UserId, Name), \nINTERLEAVE IN PARENT Users ON DELETE CASCADE\n"
  },
  {
    "path": "database/spanner/examples/migrations/1621360367_create_transactions_table.down.sql",
    "content": "DROP TABLE Transactions\n"
  },
  {
    "path": "database/spanner/examples/migrations/1621360367_create_transactions_table.up.sql",
    "content": "CREATE TABLE Transactions (\n  UserId        INT64,\n  TransactionId STRING(40),\n  Total         NUMERIC\n) PRIMARY KEY(UserId, TransactionId), \nINTERLEAVE IN PARENT Users ON DELETE CASCADE\n"
  },
  {
    "path": "database/spanner/spanner.go",
    "content": "package spanner\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\tnurl \"net/url\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"cloud.google.com/go/spanner\"\n\tsdb \"cloud.google.com/go/spanner/admin/database/apiv1\"\n\t\"cloud.google.com/go/spanner/spansql\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\n\tadminpb \"cloud.google.com/go/spanner/admin/database/apiv1/databasepb\"\n\t\"google.golang.org/api/iterator\"\n)\n\nfunc init() {\n\tdb := Spanner{}\n\tdatabase.Register(\"spanner\", &db)\n}\n\n// DefaultMigrationsTable is used if no custom table is specified\nconst DefaultMigrationsTable = \"SchemaMigrations\"\n\n// Driver errors\nvar (\n\tErrNilConfig      = errors.New(\"no config\")\n\tErrNoDatabaseName = errors.New(\"no database name\")\n\tErrNoSchema       = errors.New(\"no schema\")\n\tErrDatabaseDirty  = errors.New(\"database is dirty\")\n\tErrLockHeld       = errors.New(\"unable to obtain lock\")\n\tErrLockNotHeld    = errors.New(\"unable to release already released lock\")\n)\n\n// Config used for a Spanner instance\ntype Config struct {\n\tMigrationsTable string\n\tDatabaseName    string\n\t// Whether to parse the migration DDL with spansql before\n\t// running them towards Spanner.\n\t// Parsing outputs clean DDL statements such as reformatted\n\t// and void of comments.\n\tCleanStatements bool\n}\n\n// Spanner implements database.Driver for Google Cloud Spanner\ntype Spanner struct {\n\tdb *DB\n\n\tconfig *Config\n\n\tlock atomic.Bool\n}\n\ntype DB struct {\n\tadmin *sdb.DatabaseAdminClient\n\tdata  *spanner.Client\n}\n\nfunc NewDB(admin sdb.DatabaseAdminClient, data spanner.Client) *DB {\n\treturn &DB{\n\t\tadmin: &admin,\n\t\tdata:  &data,\n\t}\n}\n\n// WithInstance implements database.Driver\nfunc WithInstance(instance *DB, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif len(config.DatabaseName) == 0 {\n\t\treturn nil, ErrNoDatabaseName\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tsx := &Spanner{\n\t\tdb:     instance,\n\t\tconfig: config,\n\t}\n\n\tif err := sx.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sx, nil\n}\n\n// Open implements database.Driver\nfunc (s *Spanner) Open(url string) (database.Driver, error) {\n\tpurl, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tctx := context.Background()\n\n\tadminClient, err := sdb.NewDatabaseAdminClient(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdbname := strings.Replace(migrate.FilterCustomQuery(purl).String(), \"spanner://\", \"\", 1)\n\tdataClient, err := spanner.NewClient(ctx, dbname)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tmigrationsTable := purl.Query().Get(\"x-migrations-table\")\n\n\tcleanQuery := purl.Query().Get(\"x-clean-statements\")\n\tclean := false\n\tif cleanQuery != \"\" {\n\t\tclean, err = strconv.ParseBool(cleanQuery)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tdb := &DB{admin: adminClient, data: dataClient}\n\treturn WithInstance(db, &Config{\n\t\tDatabaseName:    dbname,\n\t\tMigrationsTable: migrationsTable,\n\t\tCleanStatements: clean,\n\t})\n}\n\n// Close implements database.Driver\nfunc (s *Spanner) Close() error {\n\ts.db.data.Close()\n\treturn s.db.admin.Close()\n}\n\n// Lock implements database.Driver but doesn't do anything because Spanner only\n// enqueues the UpdateDatabaseDdlRequest.\nfunc (s *Spanner) Lock() error {\n\tif swapped := s.lock.CompareAndSwap(false, true); swapped {\n\t\treturn nil\n\t}\n\treturn ErrLockHeld\n}\n\n// Unlock implements database.Driver but no action required, see Lock.\nfunc (s *Spanner) Unlock() error {\n\tif swapped := s.lock.CompareAndSwap(true, false); swapped {\n\t\treturn nil\n\t}\n\treturn ErrLockNotHeld\n}\n\n// Run implements database.Driver\nfunc (s *Spanner) Run(migration io.Reader) error {\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstmts := []string{string(migr)}\n\tif s.config.CleanStatements {\n\t\tstmts, err = cleanStatements(migr)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tctx := context.Background()\n\top, err := s.db.admin.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{\n\t\tDatabase:   s.config.DatabaseName,\n\t\tStatements: stmts,\n\t})\n\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"migration failed\", Query: migr}\n\t}\n\n\tif err := op.Wait(ctx); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"migration failed\", Query: migr}\n\t}\n\n\treturn nil\n}\n\n// SetVersion implements database.Driver\nfunc (s *Spanner) SetVersion(version int, dirty bool) error {\n\tctx := context.Background()\n\n\t_, err := s.db.data.ReadWriteTransaction(ctx,\n\t\tfunc(ctx context.Context, txn *spanner.ReadWriteTransaction) error {\n\t\t\tm := []*spanner.Mutation{\n\t\t\t\tspanner.Delete(s.config.MigrationsTable, spanner.AllKeys()),\n\t\t\t\tspanner.Insert(s.config.MigrationsTable,\n\t\t\t\t\t[]string{\"Version\", \"Dirty\"},\n\t\t\t\t\t[]interface{}{version, dirty},\n\t\t\t\t)}\n\t\t\treturn txn.BufferWrite(m)\n\t\t})\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err}\n\t}\n\n\treturn nil\n}\n\n// Version implements database.Driver\nfunc (s *Spanner) Version() (version int, dirty bool, err error) {\n\tctx := context.Background()\n\n\tstmt := spanner.Statement{\n\t\tSQL: `SELECT Version, Dirty FROM ` + s.config.MigrationsTable + ` LIMIT 1`,\n\t}\n\titer := s.db.data.Single().Query(ctx, stmt)\n\tdefer iter.Stop()\n\n\trow, err := iter.Next()\n\tswitch err {\n\tcase iterator.Done:\n\t\treturn database.NilVersion, false, nil\n\tcase nil:\n\t\tvar v int64\n\t\tif err = row.Columns(&v, &dirty); err != nil {\n\t\t\treturn 0, false, &database.Error{OrigErr: err, Query: []byte(stmt.SQL)}\n\t\t}\n\t\tversion = int(v)\n\tdefault:\n\t\treturn 0, false, &database.Error{OrigErr: err, Query: []byte(stmt.SQL)}\n\t}\n\n\treturn version, dirty, nil\n}\n\nvar nameMatcher = regexp.MustCompile(`(CREATE TABLE\\s(\\S+)\\s)|(CREATE.+INDEX\\s(\\S+)\\s)`)\n\n// Drop implements database.Driver. Retrieves the database schema first and\n// creates statements to drop the indexes and tables accordingly.\n// Note: The drop statements are created in reverse order to how they're\n// provided in the schema. Assuming the schema describes how the database can\n// be \"build up\", it seems logical to \"unbuild\" the database simply by going the\n// opposite direction. More testing\nfunc (s *Spanner) Drop() error {\n\tctx := context.Background()\n\tres, err := s.db.admin.GetDatabaseDdl(ctx, &adminpb.GetDatabaseDdlRequest{\n\t\tDatabase: s.config.DatabaseName,\n\t})\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"drop failed\"}\n\t}\n\tif len(res.Statements) == 0 {\n\t\treturn nil\n\t}\n\n\tstmts := make([]string, 0)\n\tfor i := len(res.Statements) - 1; i >= 0; i-- {\n\t\ts := res.Statements[i]\n\t\tm := nameMatcher.FindSubmatch([]byte(s))\n\n\t\tif len(m) == 0 {\n\t\t\tcontinue\n\t\t} else if tbl := m[2]; len(tbl) > 0 {\n\t\t\tstmts = append(stmts, fmt.Sprintf(`DROP TABLE %s`, tbl))\n\t\t} else if idx := m[4]; len(idx) > 0 {\n\t\t\tstmts = append(stmts, fmt.Sprintf(`DROP INDEX %s`, idx))\n\t\t}\n\t}\n\n\top, err := s.db.admin.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{\n\t\tDatabase:   s.config.DatabaseName,\n\t\tStatements: stmts,\n\t})\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(strings.Join(stmts, \"; \"))}\n\t}\n\tif err := op.Wait(ctx); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(strings.Join(stmts, \"; \"))}\n\t}\n\n\treturn nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database, which deviates from the usual\n// convention of \"caller locks\" in the Spanner type.\nfunc (s *Spanner) ensureVersionTable() (err error) {\n\tif err = s.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := s.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\tctx := context.Background()\n\ttbl := s.config.MigrationsTable\n\titer := s.db.data.Single().Read(ctx, tbl, spanner.AllKeys(), []string{\"Version\"})\n\tif err := iter.Do(func(r *spanner.Row) error { return nil }); err == nil {\n\t\treturn nil\n\t}\n\n\tstmt := fmt.Sprintf(`CREATE TABLE %s (\n    Version INT64 NOT NULL,\n    Dirty    BOOL NOT NULL\n\t) PRIMARY KEY(Version)`, tbl)\n\n\top, err := s.db.admin.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{\n\t\tDatabase:   s.config.DatabaseName,\n\t\tStatements: []string{stmt},\n\t})\n\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(stmt)}\n\t}\n\tif err := op.Wait(ctx); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(stmt)}\n\t}\n\n\treturn nil\n}\n\nfunc cleanStatements(migration []byte) ([]string, error) {\n\t// The Spanner GCP backend does not yet support comments for the UpdateDatabaseDdl RPC\n\t// (see https://issuetracker.google.com/issues/159730604) we use\n\t// spansql to parse the DDL and output valid stamements without comments\n\tddl, err := spansql.ParseDDL(\"\", string(migration))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tstmts := make([]string, 0, len(ddl.List))\n\tfor _, stmt := range ddl.List {\n\t\tstmts = append(stmts, stmt.SQL())\n\t}\n\treturn stmts, nil\n}\n"
  },
  {
    "path": "database/spanner/spanner_test.go",
    "content": "package spanner\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n\n\t\"cloud.google.com/go/spanner/spannertest\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// withSpannerEmulator is not thread-safe and cannot be used with parallel tests since it sets the emulator\nfunc withSpannerEmulator(t *testing.T, testFunc func(t *testing.T)) {\n\tt.Helper()\n\tsrv, err := spannertest.NewServer(\"localhost:0\")\n\tif err != nil {\n\t\tt.Fatal(\"Failed to create Spanner emulator:\", err)\n\t}\n\t// This is not thread-safe\n\tif err := os.Setenv(\"SPANNER_EMULATOR_HOST\", srv.Addr); err != nil {\n\t\tt.Fatal(\"Failed to set SPANNER_EMULATOR_HOST env var:\", err)\n\t}\n\tdefer srv.Close()\n\ttestFunc(t)\n\n}\n\nconst db = \"projects/abc/instances/def/databases/testdb\"\n\nfunc Test(t *testing.T) {\n\twithSpannerEmulator(t, func(t *testing.T) {\n\t\turi := fmt.Sprintf(\"spanner://%s\", db)\n\t\ts := &Spanner{}\n\t\td, err := s.Open(uri)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.Test(t, d, []byte(\"CREATE TABLE test (id BOOL) PRIMARY KEY (id)\"))\n\t})\n}\n\nfunc TestMigrate(t *testing.T) {\n\twithSpannerEmulator(t, func(t *testing.T) {\n\t\ts := &Spanner{}\n\t\turi := fmt.Sprintf(\"spanner://%s\", db)\n\t\td, err := s.Open(uri)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://./examples/migrations\", uri, d)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\t})\n}\n\nfunc TestCleanStatements(t *testing.T) {\n\ttestCases := []struct {\n\t\tname           string\n\t\tmultiStatement string\n\t\texpected       []string\n\t}{\n\t\t{\n\t\t\tname:           \"no statement\",\n\t\t\tmultiStatement: \"\",\n\t\t\texpected:       []string{},\n\t\t},\n\t\t{\n\t\t\tname:           \"single statement, single line, no semicolon, no comment\",\n\t\t\tmultiStatement: \"CREATE TABLE table_name (id STRING(255) NOT NULL) PRIMARY KEY (id)\",\n\t\t\texpected:       []string{\"CREATE TABLE table_name (\\n  id STRING(255) NOT NULL,\\n) PRIMARY KEY(id)\"},\n\t\t},\n\t\t{\n\t\t\tname: \"single statement, multi line, no semicolon, no comment\",\n\t\t\tmultiStatement: `CREATE TABLE table_name (\n\t\t\tid STRING(255) NOT NULL,\n\t\t) PRIMARY KEY (id)`,\n\t\t\texpected: []string{\"CREATE TABLE table_name (\\n  id STRING(255) NOT NULL,\\n) PRIMARY KEY(id)\"},\n\t\t},\n\t\t{\n\t\t\tname:           \"single statement, single line, with semicolon, no comment\",\n\t\t\tmultiStatement: \"CREATE TABLE table_name (id STRING(255) NOT NULL) PRIMARY KEY (id);\",\n\t\t\texpected:       []string{\"CREATE TABLE table_name (\\n  id STRING(255) NOT NULL,\\n) PRIMARY KEY(id)\"},\n\t\t},\n\t\t{\n\t\t\tname: \"single statement, multi line, with semicolon, no comment\",\n\t\t\tmultiStatement: `CREATE TABLE table_name (\n\t\t\tid STRING(255) NOT NULL,\n\t\t) PRIMARY KEY (id);`,\n\t\t\texpected: []string{\"CREATE TABLE table_name (\\n  id STRING(255) NOT NULL,\\n) PRIMARY KEY(id)\"},\n\t\t},\n\t\t{\n\t\t\tname: \"multi statement, with trailing semicolon. no comment\",\n\t\t\t// From https://github.com/mattes/migrate/pull/281\n\t\t\tmultiStatement: `CREATE TABLE table_name (\n\t\t\tid STRING(255) NOT NULL,\n\t\t) PRIMARY KEY(id);\n\n\t\tCREATE INDEX table_name_id_idx ON table_name (id);`,\n\t\t\texpected: []string{`CREATE TABLE table_name (\n  id STRING(255) NOT NULL,\n) PRIMARY KEY(id)`, \"CREATE INDEX table_name_id_idx ON table_name(id)\"},\n\t\t},\n\t\t{\n\t\t\tname: \"multi statement, no trailing semicolon, no comment\",\n\t\t\t// From https://github.com/mattes/migrate/pull/281\n\t\t\tmultiStatement: `CREATE TABLE table_name (\n\t\t\tid STRING(255) NOT NULL,\n\t\t) PRIMARY KEY(id);\n\n\t\tCREATE INDEX table_name_id_idx ON table_name (id)`,\n\t\t\texpected: []string{`CREATE TABLE table_name (\n  id STRING(255) NOT NULL,\n) PRIMARY KEY(id)`, \"CREATE INDEX table_name_id_idx ON table_name(id)\"},\n\t\t},\n\t\t{\n\t\t\tname: \"multi statement, no trailing semicolon, standalone comment\",\n\t\t\t// From https://github.com/mattes/migrate/pull/281\n\t\t\tmultiStatement: `CREATE TABLE table_name (\n\t\t\t-- standalone comment\n\t\t\tid STRING(255) NOT NULL,\n\t\t) PRIMARY KEY(id);\n\n\t\tCREATE INDEX table_name_id_idx ON table_name (id)`,\n\t\t\texpected: []string{`CREATE TABLE table_name (\n  id STRING(255) NOT NULL,\n) PRIMARY KEY(id)`, \"CREATE INDEX table_name_id_idx ON table_name(id)\"},\n\t\t},\n\t\t{\n\t\t\tname: \"multi statement, no trailing semicolon, inline comment\",\n\t\t\t// From https://github.com/mattes/migrate/pull/281\n\t\t\tmultiStatement: `CREATE TABLE table_name (\n\t\t\tid STRING(255) NOT NULL, -- inline comment\n\t\t) PRIMARY KEY(id);\n\n\t\tCREATE INDEX table_name_id_idx ON table_name (id)`,\n\t\t\texpected: []string{`CREATE TABLE table_name (\n  id STRING(255) NOT NULL,\n) PRIMARY KEY(id)`, \"CREATE INDEX table_name_id_idx ON table_name(id)\"},\n\t\t},\n\t\t{\n\t\t\tname: \"alter table with SET OPTIONS\",\n\t\t\tmultiStatement: `ALTER TABLE users ALTER COLUMN created\n\t\t\tSET OPTIONS (allow_commit_timestamp=true);`,\n\t\t\texpected: []string{\"ALTER TABLE users ALTER COLUMN created SET OPTIONS (allow_commit_timestamp = true)\"},\n\t\t},\n\t\t{\n\t\t\tname: \"column with NUMERIC type\",\n\t\t\tmultiStatement: `CREATE TABLE table_name (\n\t\t\t\tid STRING(255) NOT NULL,\n\t\t\t\tsum NUMERIC,\n\t\t\t) PRIMARY KEY (id)`,\n\t\t\texpected: []string{\"CREATE TABLE table_name (\\n  id STRING(255) NOT NULL,\\n  sum NUMERIC,\\n) PRIMARY KEY(id)\"},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tstmts, err := cleanStatements([]byte(tc.multiStatement))\n\t\t\trequire.NoError(t, err, \"Error cleaning statements\")\n\t\t\tassert.Equal(t, tc.expected, stmts)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "database/sqlcipher/README.md",
    "content": "# sqlcipher\n\nThis is just a copy of the [sqlite3](https://github.com/golang-migrate/migrate/blob/master/database/sqlite3) driver except that it imports `github.com/mutecomm/go-sqlcipher`."
  },
  {
    "path": "database/sqlcipher/examples/migrations/33_create_table.down.sql",
    "content": "DROP TABLE IF EXISTS pets;"
  },
  {
    "path": "database/sqlcipher/examples/migrations/33_create_table.up.sql",
    "content": "CREATE TABLE pets (\n  name string\n);"
  },
  {
    "path": "database/sqlcipher/examples/migrations/44_alter_table.down.sql",
    "content": "DROP TABLE IF EXISTS pets;"
  },
  {
    "path": "database/sqlcipher/examples/migrations/44_alter_table.up.sql",
    "content": "ALTER TABLE pets ADD predator bool;\n"
  },
  {
    "path": "database/sqlcipher/sqlcipher.go",
    "content": "package sqlcipher\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t_ \"github.com/mutecomm/go-sqlcipher/v4\"\n)\n\nfunc init() {\n\tdatabase.Register(\"sqlcipher\", &Sqlite{})\n}\n\nvar DefaultMigrationsTable = \"schema_migrations\"\nvar (\n\tErrDatabaseDirty  = fmt.Errorf(\"database is dirty\")\n\tErrNilConfig      = fmt.Errorf(\"no config\")\n\tErrNoDatabaseName = fmt.Errorf(\"no database name\")\n)\n\ntype Config struct {\n\tMigrationsTable string\n\tDatabaseName    string\n\tNoTxWrap        bool\n}\n\ntype Sqlite struct {\n\tdb       *sql.DB\n\tisLocked atomic.Bool\n\n\tconfig *Config\n}\n\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif err := instance.Ping(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tmx := &Sqlite{\n\t\tdb:     instance,\n\t\tconfig: config,\n\t}\n\tif err := mx.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn mx, nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database, which deviates from the usual\n// convention of \"caller locks\" in the Sqlite type.\nfunc (m *Sqlite) ensureVersionTable() (err error) {\n\tif err = m.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := m.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\tquery := fmt.Sprintf(`\n\tCREATE TABLE IF NOT EXISTS %s (version uint64,dirty bool);\n  CREATE UNIQUE INDEX IF NOT EXISTS version_unique ON %s (version);\n  `, m.config.MigrationsTable, m.config.MigrationsTable)\n\n\tif _, err := m.db.Exec(query); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (m *Sqlite) Open(url string) (database.Driver, error) {\n\tpurl, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdbfile := strings.Replace(migrate.FilterCustomQuery(purl).String(), \"sqlite3://\", \"\", 1)\n\tdb, err := sql.Open(\"sqlite3\", dbfile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tqv := purl.Query()\n\n\tmigrationsTable := qv.Get(\"x-migrations-table\")\n\tif len(migrationsTable) == 0 {\n\t\tmigrationsTable = DefaultMigrationsTable\n\t}\n\n\tnoTxWrap := false\n\tif v := qv.Get(\"x-no-tx-wrap\"); v != \"\" {\n\t\tnoTxWrap, err = strconv.ParseBool(v)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"x-no-tx-wrap: %s\", err)\n\t\t}\n\t}\n\n\tmx, err := WithInstance(db, &Config{\n\t\tDatabaseName:    purl.Path,\n\t\tMigrationsTable: migrationsTable,\n\t\tNoTxWrap:        noTxWrap,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn mx, nil\n}\n\nfunc (m *Sqlite) Close() error {\n\treturn m.db.Close()\n}\n\nfunc (m *Sqlite) Drop() (err error) {\n\tquery := `SELECT name FROM sqlite_master WHERE type = 'table';`\n\ttables, err := m.db.Query(query)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tdefer func() {\n\t\tif errClose := tables.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\ttableNames := make([]string, 0)\n\tfor tables.Next() {\n\t\tvar tableName string\n\t\tif err := tables.Scan(&tableName); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(tableName) > 0 {\n\t\t\ttableNames = append(tableNames, tableName)\n\t\t}\n\t}\n\tif err := tables.Err(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tif len(tableNames) > 0 {\n\t\tfor _, t := range tableNames {\n\t\t\tquery := \"DROP TABLE \" + t\n\t\t\terr = m.executeQuery(query)\n\t\t\tif err != nil {\n\t\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t\t}\n\t\t}\n\t\tquery := \"VACUUM\"\n\t\t_, err = m.db.Query(query)\n\t\tif err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (m *Sqlite) Lock() error {\n\tif !m.isLocked.CompareAndSwap(false, true) {\n\t\treturn database.ErrLocked\n\t}\n\treturn nil\n}\n\nfunc (m *Sqlite) Unlock() error {\n\tif !m.isLocked.CompareAndSwap(true, false) {\n\t\treturn database.ErrNotLocked\n\t}\n\treturn nil\n}\n\nfunc (m *Sqlite) Run(migration io.Reader) error {\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\tquery := string(migr[:])\n\n\tif m.config.NoTxWrap {\n\t\treturn m.executeQueryNoTx(query)\n\t}\n\treturn m.executeQuery(query)\n}\n\nfunc (m *Sqlite) executeQuery(query string) error {\n\ttx, err := m.db.Begin()\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\tif _, err := tx.Exec(query); err != nil {\n\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\terr = errors.Join(err, errRollback)\n\t\t}\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tif err := tx.Commit(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction commit failed\"}\n\t}\n\treturn nil\n}\n\nfunc (m *Sqlite) executeQueryNoTx(query string) error {\n\tif _, err := m.db.Exec(query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\treturn nil\n}\n\nfunc (m *Sqlite) SetVersion(version int, dirty bool) error {\n\ttx, err := m.db.Begin()\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\n\tquery := \"DELETE FROM \" + m.config.MigrationsTable\n\tif _, err := tx.Exec(query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\t// Also re-write the schema version for nil dirty versions to prevent\n\t// empty schema version for failed down migration on the first migration\n\t// See: https://github.com/golang-migrate/migrate/issues/330\n\tif version >= 0 || (version == database.NilVersion && dirty) {\n\t\tquery := fmt.Sprintf(`INSERT INTO %s (version, dirty) VALUES (?, ?)`, m.config.MigrationsTable)\n\t\tif _, err := tx.Exec(query, version, dirty); err != nil {\n\t\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\t\terr = errors.Join(err, errRollback)\n\t\t\t}\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction commit failed\"}\n\t}\n\n\treturn nil\n}\n\nfunc (m *Sqlite) Version() (version int, dirty bool, err error) {\n\tquery := \"SELECT version, dirty FROM \" + m.config.MigrationsTable + \" LIMIT 1\"\n\terr = m.db.QueryRow(query).Scan(&version, &dirty)\n\tif err != nil {\n\t\treturn database.NilVersion, false, nil\n\t}\n\treturn version, dirty, nil\n}\n"
  },
  {
    "path": "database/sqlcipher/sqlcipher_test.go",
    "content": "package sqlcipher\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n\t_ \"github.com/mutecomm/go-sqlcipher/v4\"\n)\n\nfunc Test(t *testing.T) {\n\tdir := t.TempDir()\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"sqlite3.db\"))\n\tp := &Sqlite{}\n\taddr := fmt.Sprintf(\"sqlite3://%s\", filepath.Join(dir, \"sqlite3.db\"))\n\td, err := p.Open(addr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdt.Test(t, d, []byte(\"CREATE TABLE t (Qty int, Name string);\"))\n}\n\nfunc TestMigrate(t *testing.T) {\n\tdir := t.TempDir()\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"sqlite3.db\"))\n\n\tdb, err := sql.Open(\"sqlite3\", filepath.Join(dir, \"sqlite3.db\"))\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\tdriver, err := WithInstance(db, &Config{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tm, err := migrate.NewWithDatabaseInstance(\n\t\t\"file://./examples/migrations\",\n\t\t\"ql\", driver)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdt.TestMigrate(t, m)\n}\n\nfunc TestMigrationTable(t *testing.T) {\n\tdir := t.TempDir()\n\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"sqlite3.db\"))\n\n\tdb, err := sql.Open(\"sqlite3\", filepath.Join(dir, \"sqlite3.db\"))\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\n\tconfig := &Config{\n\t\tMigrationsTable: \"my_migration_table\",\n\t}\n\tdriver, err := WithInstance(db, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm, err := migrate.NewWithDatabaseInstance(\n\t\t\"file://./examples/migrations\",\n\t\t\"ql\", driver)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(\"UP\")\n\terr = m.Up()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, err = db.Query(fmt.Sprintf(\"SELECT * FROM %s\", config.MigrationsTable))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestNoTxWrap(t *testing.T) {\n\tdir := t.TempDir()\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"sqlite3.db\"))\n\tp := &Sqlite{}\n\taddr := fmt.Sprintf(\"sqlite3://%s?x-no-tx-wrap=true\", filepath.Join(dir, \"sqlite3.db\"))\n\td, err := p.Open(addr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// An explicit BEGIN statement would ordinarily fail without x-no-tx-wrap.\n\t// (Transactions in sqlite may not be nested.)\n\tdt.Test(t, d, []byte(\"BEGIN; CREATE TABLE t (Qty int, Name string); COMMIT;\"))\n}\n\nfunc TestNoTxWrapInvalidValue(t *testing.T) {\n\tdir := t.TempDir()\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"sqlite3.db\"))\n\tp := &Sqlite{}\n\taddr := fmt.Sprintf(\"sqlite3://%s?x-no-tx-wrap=yeppers\", filepath.Join(dir, \"sqlite3.db\"))\n\t_, err := p.Open(addr)\n\tif assert.Error(t, err) {\n\t\tassert.Contains(t, err.Error(), \"x-no-tx-wrap\")\n\t\tassert.Contains(t, err.Error(), \"invalid syntax\")\n\t}\n}\n"
  },
  {
    "path": "database/sqlite/README.md",
    "content": "# sqlite\n\n`sqlite://path/to/database?query`\n\nUnlike other migrate database drivers, the sqlite driver will automatically wrap each migration in an implicit transaction by default.  Migrations must not contain explicit `BEGIN` or `COMMIT` statements.  This behavior may change in a future major release.  (See below for a workaround.)\n\nThe auxiliary query parameters listed below may be supplied to tailor migrate behavior.  All auxiliary query parameters are optional.\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| `x-migrations-table` | `MigrationsTable` | Name of the migrations table.  Defaults to `schema_migrations`. |\n| `x-no-tx-wrap` | `NoTxWrap` | Disable implicit transactions when `true`.  Migrations may, and should, contain explicit `BEGIN` and `COMMIT` statements. |\n\n## Notes\n\n* Uses the `modernc.org/sqlite` sqlite db driver (pure Go)\n  * Has [limited `GOOS` and `GOARCH` support](https://pkg.go.dev/modernc.org/sqlite?utm_source=godoc#hdr-Supported_platforms_and_architectures)\n"
  },
  {
    "path": "database/sqlite/examples/migrations/33_create_table.down.sql",
    "content": "DROP TABLE IF EXISTS pets;"
  },
  {
    "path": "database/sqlite/examples/migrations/33_create_table.up.sql",
    "content": "CREATE TABLE pets (\n  name string\n);"
  },
  {
    "path": "database/sqlite/examples/migrations/44_alter_table.down.sql",
    "content": "DROP TABLE IF EXISTS pets;"
  },
  {
    "path": "database/sqlite/examples/migrations/44_alter_table.up.sql",
    "content": "ALTER TABLE pets ADD predator bool;\n"
  },
  {
    "path": "database/sqlite/sqlite.go",
    "content": "package sqlite\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t_ \"modernc.org/sqlite\"\n)\n\nfunc init() {\n\tdatabase.Register(\"sqlite\", &Sqlite{})\n}\n\nvar DefaultMigrationsTable = \"schema_migrations\"\nvar (\n\tErrDatabaseDirty  = fmt.Errorf(\"database is dirty\")\n\tErrNilConfig      = fmt.Errorf(\"no config\")\n\tErrNoDatabaseName = fmt.Errorf(\"no database name\")\n)\n\ntype Config struct {\n\tMigrationsTable string\n\tDatabaseName    string\n\tNoTxWrap        bool\n}\n\ntype Sqlite struct {\n\tdb       *sql.DB\n\tisLocked atomic.Bool\n\n\tconfig *Config\n}\n\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif err := instance.Ping(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tmx := &Sqlite{\n\t\tdb:     instance,\n\t\tconfig: config,\n\t}\n\tif err := mx.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn mx, nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database, which deviates from the usual\n// convention of \"caller locks\" in the Sqlite type.\nfunc (m *Sqlite) ensureVersionTable() (err error) {\n\tif err = m.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := m.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\tquery := fmt.Sprintf(`\n\tCREATE TABLE IF NOT EXISTS %s (version uint64,dirty bool);\n  CREATE UNIQUE INDEX IF NOT EXISTS version_unique ON %s (version);\n  `, m.config.MigrationsTable, m.config.MigrationsTable)\n\n\tif _, err := m.db.Exec(query); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (m *Sqlite) Open(url string) (database.Driver, error) {\n\tpurl, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdbfile := strings.Replace(migrate.FilterCustomQuery(purl).String(), \"sqlite://\", \"\", 1)\n\tdb, err := sql.Open(\"sqlite\", dbfile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tqv := purl.Query()\n\n\tmigrationsTable := qv.Get(\"x-migrations-table\")\n\tif len(migrationsTable) == 0 {\n\t\tmigrationsTable = DefaultMigrationsTable\n\t}\n\n\tnoTxWrap := false\n\tif v := qv.Get(\"x-no-tx-wrap\"); v != \"\" {\n\t\tnoTxWrap, err = strconv.ParseBool(v)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"x-no-tx-wrap: %s\", err)\n\t\t}\n\t}\n\n\tmx, err := WithInstance(db, &Config{\n\t\tDatabaseName:    purl.Path,\n\t\tMigrationsTable: migrationsTable,\n\t\tNoTxWrap:        noTxWrap,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn mx, nil\n}\n\nfunc (m *Sqlite) Close() error {\n\treturn m.db.Close()\n}\n\nfunc (m *Sqlite) Drop() (err error) {\n\tquery := `SELECT name FROM sqlite_master WHERE type = 'table';`\n\ttables, err := m.db.Query(query)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tdefer func() {\n\t\tif errClose := tables.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\ttableNames := make([]string, 0)\n\tfor tables.Next() {\n\t\tvar tableName string\n\t\tif err := tables.Scan(&tableName); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(tableName) > 0 {\n\t\t\ttableNames = append(tableNames, tableName)\n\t\t}\n\t}\n\tif err := tables.Err(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tif len(tableNames) > 0 {\n\t\tfor _, t := range tableNames {\n\t\t\tquery := \"DROP TABLE \" + t\n\t\t\terr = m.executeQuery(query)\n\t\t\tif err != nil {\n\t\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t\t}\n\t\t}\n\t\tquery := \"VACUUM\"\n\t\t_, err = m.db.Query(query)\n\t\tif err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (m *Sqlite) Lock() error {\n\tif !m.isLocked.CompareAndSwap(false, true) {\n\t\treturn database.ErrLocked\n\t}\n\treturn nil\n}\n\nfunc (m *Sqlite) Unlock() error {\n\tif !m.isLocked.CompareAndSwap(true, false) {\n\t\treturn database.ErrNotLocked\n\t}\n\treturn nil\n}\n\nfunc (m *Sqlite) Run(migration io.Reader) error {\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\tquery := string(migr[:])\n\n\tif m.config.NoTxWrap {\n\t\treturn m.executeQueryNoTx(query)\n\t}\n\treturn m.executeQuery(query)\n}\n\nfunc (m *Sqlite) executeQuery(query string) error {\n\ttx, err := m.db.Begin()\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\tif _, err := tx.Exec(query); err != nil {\n\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\terr = errors.Join(err, errRollback)\n\t\t}\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tif err := tx.Commit(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction commit failed\"}\n\t}\n\treturn nil\n}\n\nfunc (m *Sqlite) executeQueryNoTx(query string) error {\n\tif _, err := m.db.Exec(query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\treturn nil\n}\n\nfunc (m *Sqlite) SetVersion(version int, dirty bool) error {\n\ttx, err := m.db.Begin()\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\n\tquery := \"DELETE FROM \" + m.config.MigrationsTable\n\tif _, err := tx.Exec(query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\t// Also re-write the schema version for nil dirty versions to prevent\n\t// empty schema version for failed down migration on the first migration\n\t// See: https://github.com/golang-migrate/migrate/issues/330\n\tif version >= 0 || (version == database.NilVersion && dirty) {\n\t\tquery := fmt.Sprintf(`INSERT INTO %s (version, dirty) VALUES (?, ?)`, m.config.MigrationsTable)\n\t\tif _, err := tx.Exec(query, version, dirty); err != nil {\n\t\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\t\terr = errors.Join(err, errRollback)\n\t\t\t}\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction commit failed\"}\n\t}\n\n\treturn nil\n}\n\nfunc (m *Sqlite) Version() (version int, dirty bool, err error) {\n\tquery := \"SELECT version, dirty FROM \" + m.config.MigrationsTable + \" LIMIT 1\"\n\terr = m.db.QueryRow(query).Scan(&version, &dirty)\n\tif err != nil {\n\t\treturn database.NilVersion, false, nil\n\t}\n\treturn version, dirty, nil\n}\n"
  },
  {
    "path": "database/sqlite/sqlite_test.go",
    "content": "package sqlite\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n\t_ \"modernc.org/sqlite\"\n)\n\nfunc Test(t *testing.T) {\n\tdir := t.TempDir()\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"sqlite.db\"))\n\tp := &Sqlite{}\n\taddr := fmt.Sprintf(\"sqlite://%s\", filepath.Join(dir, \"sqlite.db\"))\n\td, err := p.Open(addr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdt.Test(t, d, []byte(\"CREATE TABLE t (Qty int, Name string);\"))\n}\n\nfunc TestMigrate(t *testing.T) {\n\tdir := t.TempDir()\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"sqlite.db\"))\n\n\tdb, err := sql.Open(\"sqlite\", filepath.Join(dir, \"sqlite.db\"))\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\tdriver, err := WithInstance(db, &Config{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tm, err := migrate.NewWithDatabaseInstance(\n\t\t\"file://./examples/migrations\",\n\t\t\"ql\", driver)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdt.TestMigrate(t, m)\n}\n\nfunc TestMigrationTable(t *testing.T) {\n\tdir := t.TempDir()\n\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"sqlite.db\"))\n\n\tdb, err := sql.Open(\"sqlite\", filepath.Join(dir, \"sqlite.db\"))\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\n\tconfig := &Config{\n\t\tMigrationsTable: \"my_migration_table\",\n\t}\n\tdriver, err := WithInstance(db, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm, err := migrate.NewWithDatabaseInstance(\n\t\t\"file://./examples/migrations\",\n\t\t\"ql\", driver)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(\"UP\")\n\terr = m.Up()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, err = db.Query(fmt.Sprintf(\"SELECT * FROM %s\", config.MigrationsTable))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestNoTxWrap(t *testing.T) {\n\tdir := t.TempDir()\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"sqlite.db\"))\n\tp := &Sqlite{}\n\taddr := fmt.Sprintf(\"sqlite://%s?x-no-tx-wrap=true\", filepath.Join(dir, \"sqlite.db\"))\n\td, err := p.Open(addr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// An explicit BEGIN statement would ordinarily fail without x-no-tx-wrap.\n\t// (Transactions in sqlite may not be nested.)\n\tdt.Test(t, d, []byte(\"BEGIN; CREATE TABLE t (Qty int, Name string); COMMIT;\"))\n}\n\nfunc TestNoTxWrapInvalidValue(t *testing.T) {\n\tdir := t.TempDir()\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"sqlite.db\"))\n\tp := &Sqlite{}\n\taddr := fmt.Sprintf(\"sqlite://%s?x-no-tx-wrap=yeppers\", filepath.Join(dir, \"sqlite.db\"))\n\t_, err := p.Open(addr)\n\tif assert.Error(t, err) {\n\t\tassert.Contains(t, err.Error(), \"x-no-tx-wrap\")\n\t\tassert.Contains(t, err.Error(), \"invalid syntax\")\n\t}\n}\n\nfunc TestMigrateWithDirectoryNameContainsWhitespaces(t *testing.T) {\n\tdir := t.TempDir()\n\tdbPath := filepath.Join(dir, \"sqlite.db\")\n\tt.Logf(\"DB path : %s\\n\", dbPath)\n\tp := &Sqlite{}\n\taddr := fmt.Sprintf(\"sqlite://file:%s\", dbPath)\n\td, err := p.Open(addr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdt.Test(t, d, []byte(\"CREATE TABLE t (Qty int, Name string);\"))\n}\n"
  },
  {
    "path": "database/sqlite3/README.md",
    "content": "# sqlite3\n\n`sqlite3://path/to/database?query`\n\nUnlike other migrate database drivers, the sqlite3 driver will automatically wrap each migration in an implicit transaction by default.  Migrations must not contain explicit `BEGIN` or `COMMIT` statements.  This behavior may change in a future major release.  (See below for a workaround.)\n\nRefer to [upstream documentation](https://github.com/mattn/go-sqlite3/blob/master/README.md#connection-string) for a complete list of query parameters supported by the sqlite3 database driver.  The auxiliary query parameters listed below may be supplied to tailor migrate behavior.  All auxiliary query parameters are optional.\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| `x-migrations-table` | `MigrationsTable` | Name of the migrations table.  Defaults to `schema_migrations`. |\n| `x-no-tx-wrap` | `NoTxWrap` | Disable implicit transactions when `true`.  Migrations may, and should, contain explicit `BEGIN` and `COMMIT` statements. |\n\n## Notes\n\n* Uses the `github.com/mattn/go-sqlite3` sqlite db driver (cgo)\n"
  },
  {
    "path": "database/sqlite3/examples/migrations/33_create_table.down.sql",
    "content": "DROP TABLE IF EXISTS pets;"
  },
  {
    "path": "database/sqlite3/examples/migrations/33_create_table.up.sql",
    "content": "CREATE TABLE pets (\n  name string\n);"
  },
  {
    "path": "database/sqlite3/examples/migrations/44_alter_table.down.sql",
    "content": "DROP TABLE IF EXISTS pets;"
  },
  {
    "path": "database/sqlite3/examples/migrations/44_alter_table.up.sql",
    "content": "ALTER TABLE pets ADD predator bool;\n"
  },
  {
    "path": "database/sqlite3/sqlite3.go",
    "content": "package sqlite3\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t_ \"github.com/mattn/go-sqlite3\"\n)\n\nfunc init() {\n\tdatabase.Register(\"sqlite3\", &Sqlite{})\n}\n\nvar DefaultMigrationsTable = \"schema_migrations\"\nvar (\n\tErrDatabaseDirty  = fmt.Errorf(\"database is dirty\")\n\tErrNilConfig      = fmt.Errorf(\"no config\")\n\tErrNoDatabaseName = fmt.Errorf(\"no database name\")\n)\n\ntype Config struct {\n\tMigrationsTable string\n\tDatabaseName    string\n\tNoTxWrap        bool\n}\n\ntype Sqlite struct {\n\tdb       *sql.DB\n\tisLocked atomic.Bool\n\n\tconfig *Config\n}\n\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif err := instance.Ping(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tmx := &Sqlite{\n\t\tdb:     instance,\n\t\tconfig: config,\n\t}\n\tif err := mx.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn mx, nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database, which deviates from the usual\n// convention of \"caller locks\" in the Sqlite type.\nfunc (m *Sqlite) ensureVersionTable() (err error) {\n\tif err = m.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := m.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\tquery := fmt.Sprintf(`\n\tCREATE TABLE IF NOT EXISTS %s (version uint64,dirty bool);\n  CREATE UNIQUE INDEX IF NOT EXISTS version_unique ON %s (version);\n  `, m.config.MigrationsTable, m.config.MigrationsTable)\n\n\tif _, err := m.db.Exec(query); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (m *Sqlite) Open(url string) (database.Driver, error) {\n\tpurl, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdbfile := strings.Replace(migrate.FilterCustomQuery(purl).String(), \"sqlite3://\", \"\", 1)\n\tdb, err := sql.Open(\"sqlite3\", dbfile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tqv := purl.Query()\n\n\tmigrationsTable := qv.Get(\"x-migrations-table\")\n\tif len(migrationsTable) == 0 {\n\t\tmigrationsTable = DefaultMigrationsTable\n\t}\n\n\tnoTxWrap := false\n\tif v := qv.Get(\"x-no-tx-wrap\"); v != \"\" {\n\t\tnoTxWrap, err = strconv.ParseBool(v)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"x-no-tx-wrap: %s\", err)\n\t\t}\n\t}\n\n\tmx, err := WithInstance(db, &Config{\n\t\tDatabaseName:    purl.Path,\n\t\tMigrationsTable: migrationsTable,\n\t\tNoTxWrap:        noTxWrap,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn mx, nil\n}\n\nfunc (m *Sqlite) Close() error {\n\treturn m.db.Close()\n}\n\nfunc (m *Sqlite) Drop() (err error) {\n\tquery := `SELECT name FROM sqlite_master WHERE type = 'table';`\n\ttables, err := m.db.Query(query)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tdefer func() {\n\t\tif errClose := tables.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\ttableNames := make([]string, 0)\n\tfor tables.Next() {\n\t\tvar tableName string\n\t\tif err := tables.Scan(&tableName); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(tableName) > 0 {\n\t\t\ttableNames = append(tableNames, tableName)\n\t\t}\n\t}\n\tif err := tables.Err(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tif len(tableNames) > 0 {\n\t\tfor _, t := range tableNames {\n\t\t\tquery := \"DROP TABLE \" + t\n\t\t\terr = m.executeQuery(query)\n\t\t\tif err != nil {\n\t\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t\t}\n\t\t}\n\t\tquery := \"VACUUM\"\n\t\t_, err = m.db.Query(query)\n\t\tif err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (m *Sqlite) Lock() error {\n\tif !m.isLocked.CompareAndSwap(false, true) {\n\t\treturn database.ErrLocked\n\t}\n\treturn nil\n}\n\nfunc (m *Sqlite) Unlock() error {\n\tif !m.isLocked.CompareAndSwap(true, false) {\n\t\treturn database.ErrNotLocked\n\t}\n\treturn nil\n}\n\nfunc (m *Sqlite) Run(migration io.Reader) error {\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\tquery := string(migr[:])\n\n\tif m.config.NoTxWrap {\n\t\treturn m.executeQueryNoTx(query)\n\t}\n\treturn m.executeQuery(query)\n}\n\nfunc (m *Sqlite) executeQuery(query string) error {\n\ttx, err := m.db.Begin()\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\tif _, err := tx.Exec(query); err != nil {\n\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\terr = errors.Join(err, errRollback)\n\t\t}\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tif err := tx.Commit(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction commit failed\"}\n\t}\n\treturn nil\n}\n\nfunc (m *Sqlite) executeQueryNoTx(query string) error {\n\tif _, err := m.db.Exec(query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\treturn nil\n}\n\nfunc (m *Sqlite) SetVersion(version int, dirty bool) error {\n\ttx, err := m.db.Begin()\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\n\tquery := \"DELETE FROM \" + m.config.MigrationsTable\n\tif _, err := tx.Exec(query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\t// Also re-write the schema version for nil dirty versions to prevent\n\t// empty schema version for failed down migration on the first migration\n\t// See: https://github.com/golang-migrate/migrate/issues/330\n\tif version >= 0 || (version == database.NilVersion && dirty) {\n\t\tquery := fmt.Sprintf(`INSERT INTO %s (version, dirty) VALUES (?, ?)`, m.config.MigrationsTable)\n\t\tif _, err := tx.Exec(query, version, dirty); err != nil {\n\t\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\t\terr = errors.Join(err, errRollback)\n\t\t\t}\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction commit failed\"}\n\t}\n\n\treturn nil\n}\n\nfunc (m *Sqlite) Version() (version int, dirty bool, err error) {\n\tquery := \"SELECT version, dirty FROM \" + m.config.MigrationsTable + \" LIMIT 1\"\n\terr = m.db.QueryRow(query).Scan(&version, &dirty)\n\tif err != nil {\n\t\treturn database.NilVersion, false, nil\n\t}\n\treturn version, dirty, nil\n}\n"
  },
  {
    "path": "database/sqlite3/sqlite3_test.go",
    "content": "package sqlite3\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n\t_ \"github.com/mattn/go-sqlite3\"\n)\n\nfunc Test(t *testing.T) {\n\tdir := t.TempDir()\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"sqlite3.db\"))\n\tp := &Sqlite{}\n\taddr := fmt.Sprintf(\"sqlite3://%s\", filepath.Join(dir, \"sqlite3.db\"))\n\td, err := p.Open(addr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdt.Test(t, d, []byte(\"CREATE TABLE t (Qty int, Name string);\"))\n}\n\nfunc TestMigrate(t *testing.T) {\n\tdir := t.TempDir()\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"sqlite3.db\"))\n\n\tdb, err := sql.Open(\"sqlite3\", filepath.Join(dir, \"sqlite3.db\"))\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\tdriver, err := WithInstance(db, &Config{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tm, err := migrate.NewWithDatabaseInstance(\n\t\t\"file://./examples/migrations\",\n\t\t\"ql\", driver)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdt.TestMigrate(t, m)\n}\n\nfunc TestMigrationTable(t *testing.T) {\n\tdir := t.TempDir()\n\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"sqlite3.db\"))\n\n\tdb, err := sql.Open(\"sqlite3\", filepath.Join(dir, \"sqlite3.db\"))\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\n\tconfig := &Config{\n\t\tMigrationsTable: \"my_migration_table\",\n\t}\n\tdriver, err := WithInstance(db, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm, err := migrate.NewWithDatabaseInstance(\n\t\t\"file://./examples/migrations\",\n\t\t\"ql\", driver)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(\"UP\")\n\terr = m.Up()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, err = db.Query(fmt.Sprintf(\"SELECT * FROM %s\", config.MigrationsTable))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestNoTxWrap(t *testing.T) {\n\tdir := t.TempDir()\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"sqlite3.db\"))\n\tp := &Sqlite{}\n\taddr := fmt.Sprintf(\"sqlite3://%s?x-no-tx-wrap=true\", filepath.Join(dir, \"sqlite3.db\"))\n\td, err := p.Open(addr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// An explicit BEGIN statement would ordinarily fail without x-no-tx-wrap.\n\t// (Transactions in sqlite may not be nested.)\n\tdt.Test(t, d, []byte(\"BEGIN; CREATE TABLE t (Qty int, Name string); COMMIT;\"))\n}\n\nfunc TestNoTxWrapInvalidValue(t *testing.T) {\n\tdir := t.TempDir()\n\tt.Logf(\"DB path : %s\\n\", filepath.Join(dir, \"sqlite3.db\"))\n\tp := &Sqlite{}\n\taddr := fmt.Sprintf(\"sqlite3://%s?x-no-tx-wrap=yeppers\", filepath.Join(dir, \"sqlite3.db\"))\n\t_, err := p.Open(addr)\n\tif assert.Error(t, err) {\n\t\tassert.Contains(t, err.Error(), \"x-no-tx-wrap\")\n\t\tassert.Contains(t, err.Error(), \"invalid syntax\")\n\t}\n}\n\nfunc TestMigrateWithDirectoryNameContainsWhitespaces(t *testing.T) {\n\tdir := t.TempDir()\n\tdbPath := filepath.Join(dir, \"sqlite3.db\")\n\tt.Logf(\"DB path : %s\\n\", dbPath)\n\tp := &Sqlite{}\n\taddr := fmt.Sprintf(\"sqlite3://file:%s\", dbPath)\n\td, err := p.Open(addr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdt.Test(t, d, []byte(\"CREATE TABLE t (Qty int, Name string);\"))\n}\n"
  },
  {
    "path": "database/sqlserver/README.md",
    "content": "# Microsoft SQL Server\n\n`sqlserver://username:password@host/instance?param1=value&param2=value`\n`sqlserver://username:password@host:port?param1=value&param2=value`\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |\n| `username` | |  enter the SQL Server Authentication user id or the Windows Authentication user id in the DOMAIN\\User format. On Windows, if user id is empty or missing Single-Sign-On is used. |\n| `password` | | The user's password. | \n| `host` | | The host to connect to. |\n| `port` | | The port to connect to. |\n| `instance` | | SQL Server instance name. |\n| `database` | `DatabaseName` | The name of the database to connect to |\n| `connection+timeout` | | in seconds (default is 0 for no timeout), set to 0 for no timeout. |\n| `dial+timeout` | | in seconds (default is 15), set to 0 for no timeout. |\n| `encrypt` | | `disable` - Data send between client and server is not encrypted. `false` - Data sent between client and server is not encrypted beyond the login packet (Default). `true` - Data sent between client and server is encrypted. |\n| `app+name` || The application name (default is go-mssqldb). |\n| `useMsi` | | `true` - Use Azure MSI Authentication for connecting to Sql Server. Must be running from an Azure VM/an instance with MSI enabled. `false` - Use password authentication (Default). See [here for Azure MSI Auth details](https://docs.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-connect-msi). NOTE: Since this cannot be tested locally, this is not officially supported.\n\nSee https://github.com/microsoft/go-mssqldb for full parameter list.\n\n## Driver Support\n\n### Which go-mssqldb driver to us?\n\nPlease note that the deprecated `mssql` driver is not supported. Please use the newer `sqlserver` driver.  \nSee https://github.com/microsoft/go-mssqldb#deprecated for more information.\n\n### Official Support by migrate\n\nVersions of MS SQL Server 2019 newer than CTP3.1 are not officially supported since there are issues testing against the Docker image.\nFor more info, see: https://github.com/golang-migrate/migrate/issues/160#issuecomment-522433269\n"
  },
  {
    "path": "database/sqlserver/examples/migrations/1085649617_create_users_table.down.sql",
    "content": "DROP TABLE IF EXISTS users;\n"
  },
  {
    "path": "database/sqlserver/examples/migrations/1085649617_create_users_table.up.sql",
    "content": "CREATE TABLE users (\n  user_id integer unique,\n  name    varchar(40),\n  email   varchar(40)\n);\n"
  },
  {
    "path": "database/sqlserver/examples/migrations/1185749658_add_city_to_users.down.sql",
    "content": "ALTER TABLE users DROP COLUMN IF EXISTS city;\n"
  },
  {
    "path": "database/sqlserver/examples/migrations/1185749658_add_city_to_users.up.sql",
    "content": "ALTER TABLE users ADD city varchar(100);\n\n\n"
  },
  {
    "path": "database/sqlserver/examples/migrations/1285849751_add_index_on_user_emails.down.sql",
    "content": "DROP INDEX IF EXISTS users_email_index;\n"
  },
  {
    "path": "database/sqlserver/examples/migrations/1285849751_add_index_on_user_emails.up.sql",
    "content": "CREATE UNIQUE INDEX users_email_index ON users (email);\n\n-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/sqlserver/examples/migrations/1385949617_create_books_table.down.sql",
    "content": "DROP TABLE IF EXISTS books;\n"
  },
  {
    "path": "database/sqlserver/examples/migrations/1385949617_create_books_table.up.sql",
    "content": "CREATE TABLE books (\n  user_id integer,\n  name    varchar(40),\n  author  varchar(40)\n);\n"
  },
  {
    "path": "database/sqlserver/examples/migrations/1485949617_create_movies_table.down.sql",
    "content": "DROP TABLE IF EXISTS movies;\n"
  },
  {
    "path": "database/sqlserver/examples/migrations/1485949617_create_movies_table.up.sql",
    "content": "CREATE TABLE movies (\n  user_id   integer,\n  name      varchar(40),\n  director  varchar(40)\n);\n"
  },
  {
    "path": "database/sqlserver/examples/migrations/1585849751_just_a_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/sqlserver/examples/migrations/1685849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/sqlserver/examples/migrations/1785849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/sqlserver/examples/migrations/1885849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/sqlserver/sqlserver.go",
    "content": "package sqlserver\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/Azure/go-autorest/autorest/adal\"\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\tmssql \"github.com/microsoft/go-mssqldb\" // mssql support\n)\n\nfunc init() {\n\tdatabase.Register(\"sqlserver\", &SQLServer{})\n}\n\n// DefaultMigrationsTable is the name of the migrations table in the database\nvar DefaultMigrationsTable = \"schema_migrations\"\n\nvar (\n\tErrNilConfig                 = fmt.Errorf(\"no config\")\n\tErrNoDatabaseName            = fmt.Errorf(\"no database name\")\n\tErrNoSchema                  = fmt.Errorf(\"no schema\")\n\tErrDatabaseDirty             = fmt.Errorf(\"database is dirty\")\n\tErrMultipleAuthOptionsPassed = fmt.Errorf(\"both password and useMsi=true were passed\")\n)\n\nvar lockErrorMap = map[int]string{\n\t-1:   \"The lock request timed out.\",\n\t-2:   \"The lock request was canceled.\",\n\t-3:   \"The lock request was chosen as a deadlock victim.\",\n\t-999: \"Parameter validation or other call error.\",\n}\n\n// Config for database\ntype Config struct {\n\tMigrationsTable string\n\tDatabaseName    string\n\tSchemaName      string\n}\n\n// SQL Server connection\ntype SQLServer struct {\n\t// Locking and unlocking need to use the same connection\n\tconn     *sql.Conn\n\tdb       *sql.DB\n\tisLocked atomic.Bool\n\n\t// Open and WithInstance need to garantuee that config is never nil\n\tconfig *Config\n}\n\n// WithInstance returns a database instance from an already created database connection.\n//\n// Note that the deprecated `mssql` driver is not supported. Please use the newer `sqlserver` driver.\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif err := instance.Ping(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif config.DatabaseName == \"\" {\n\t\tquery := `SELECT DB_NAME()`\n\t\tvar databaseName string\n\t\tif err := instance.QueryRow(query).Scan(&databaseName); err != nil {\n\t\t\treturn nil, &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\tif len(databaseName) == 0 {\n\t\t\treturn nil, ErrNoDatabaseName\n\t\t}\n\n\t\tconfig.DatabaseName = databaseName\n\t}\n\n\tif config.SchemaName == \"\" {\n\t\tquery := `SELECT SCHEMA_NAME()`\n\t\tvar schemaName string\n\t\tif err := instance.QueryRow(query).Scan(&schemaName); err != nil {\n\t\t\treturn nil, &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\tif len(schemaName) == 0 {\n\t\t\treturn nil, ErrNoSchema\n\t\t}\n\n\t\tconfig.SchemaName = schemaName\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tconn, err := instance.Conn(context.Background())\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tss := &SQLServer{\n\t\tconn:   conn,\n\t\tdb:     instance,\n\t\tconfig: config,\n\t}\n\n\tif err := ss.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ss, nil\n}\n\n// Open a connection to the database.\nfunc (ss *SQLServer) Open(url string) (database.Driver, error) {\n\tpurl, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tuseMsiParam := purl.Query().Get(\"useMsi\")\n\tuseMsi := false\n\tif len(useMsiParam) > 0 {\n\t\tuseMsi, err = strconv.ParseBool(useMsiParam)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif _, isPasswordSet := purl.User.Password(); useMsi && isPasswordSet {\n\t\treturn nil, ErrMultipleAuthOptionsPassed\n\t}\n\n\tfilteredURL := migrate.FilterCustomQuery(purl).String()\n\n\tvar db *sql.DB\n\tif useMsi {\n\t\tresource := getAADResourceFromServerUri(purl)\n\t\ttokenProvider, err := getMSITokenProvider(resource)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tconnector, err := mssql.NewAccessTokenConnector(\n\t\t\tfilteredURL, tokenProvider)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tdb = sql.OpenDB(connector)\n\n\t} else {\n\t\tdb, err = sql.Open(\"sqlserver\", filteredURL)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tmigrationsTable := purl.Query().Get(\"x-migrations-table\")\n\n\tpx, err := WithInstance(db, &Config{\n\t\tDatabaseName:    purl.Path,\n\t\tMigrationsTable: migrationsTable,\n\t})\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\n// Close the database connection\nfunc (ss *SQLServer) Close() error {\n\tconnErr := ss.conn.Close()\n\tdbErr := ss.db.Close()\n\tif connErr != nil || dbErr != nil {\n\t\treturn fmt.Errorf(\"conn: %v, db: %v\", connErr, dbErr)\n\t}\n\treturn nil\n}\n\n// Lock creates an advisory local on the database to prevent multiple migrations from running at the same time.\nfunc (ss *SQLServer) Lock() error {\n\treturn database.CasRestoreOnErr(&ss.isLocked, false, true, database.ErrLocked, func() error {\n\t\taid, err := database.GenerateAdvisoryLockId(ss.config.DatabaseName, ss.config.SchemaName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// This will block until the lock is acquired.\n\t\t// MS Docs: sp_getapplock: https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-getapplock-transact-sql?view=sql-server-2017\n\t\tquery := `\n\t\tDECLARE @lockResult int;\n\t\tEXEC @lockResult = sp_getapplock @Resource = @p1, @LockMode = 'Exclusive', @LockOwner = 'Session', @LockTimeout = -1;\n\t\tSELECT @lockResult;`\n\n\t\tvar status int\n\t\tif err = ss.conn.QueryRowContext(context.Background(), query, aid).Scan(&status); err == nil && status > -1 {\n\t\t\treturn nil\n\t\t} else if err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Err: \"try lock failed\", Query: []byte(query)}\n\t\t} else {\n\t\t\terrorDescription, ok := lockErrorMap[status]\n\t\t\tif !ok {\n\t\t\t\terrorDescription = \"Unknown error\"\n\t\t\t}\n\t\t\treturn &database.Error{Err: fmt.Sprintf(\"try lock failed with error %v: %v\", status, errorDescription), Query: []byte(query)}\n\t\t}\n\t})\n}\n\n// Unlock froms the migration lock from the database\nfunc (ss *SQLServer) Unlock() error {\n\treturn database.CasRestoreOnErr(&ss.isLocked, true, false, database.ErrNotLocked, func() error {\n\t\taid, err := database.GenerateAdvisoryLockId(ss.config.DatabaseName, ss.config.SchemaName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// MS Docs: sp_releaseapplock: https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-releaseapplock-transact-sql?view=sql-server-2017\n\t\tquery := `EXEC sp_releaseapplock @Resource = @p1, @LockOwner = 'Session'`\n\t\tif _, err := ss.conn.ExecContext(context.Background(), query, aid); err != nil {\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\n// Run the migrations for the database\nfunc (ss *SQLServer) Run(migration io.Reader) error {\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// run migration\n\tquery := string(migr[:])\n\tif _, err := ss.conn.ExecContext(context.Background(), query); err != nil {\n\t\tif msErr, ok := err.(mssql.Error); ok {\n\t\t\tmessage := fmt.Sprintf(\"migration failed: %s\", msErr.Message)\n\t\t\tif msErr.ProcName != \"\" {\n\t\t\t\tmessage = fmt.Sprintf(\"%s (proc name %s)\", msErr.Message, msErr.ProcName)\n\t\t\t}\n\t\t\treturn database.Error{OrigErr: err, Err: message, Query: migr, Line: uint(msErr.LineNo)}\n\t\t}\n\t\treturn database.Error{OrigErr: err, Err: \"migration failed\", Query: migr}\n\t}\n\n\treturn nil\n}\n\n// SetVersion for the current database\nfunc (ss *SQLServer) SetVersion(version int, dirty bool) error {\n\n\ttx, err := ss.conn.BeginTx(context.Background(), &sql.TxOptions{})\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction start failed\"}\n\t}\n\n\tquery := `TRUNCATE TABLE ` + ss.getMigrationTable()\n\tif _, err := tx.Exec(query); err != nil {\n\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\terr = errors.Join(err, errRollback)\n\t\t}\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\t// Also re-write the schema version for nil dirty versions to prevent\n\t// empty schema version for failed down migration on the first migration\n\t// See: https://github.com/golang-migrate/migrate/issues/330\n\tif version >= 0 || (version == database.NilVersion && dirty) {\n\t\tvar dirtyBit int\n\t\tif dirty {\n\t\t\tdirtyBit = 1\n\t\t}\n\t\tquery = `INSERT INTO ` + ss.getMigrationTable() + ` (version, dirty) VALUES (@p1, @p2)`\n\t\tif _, err := tx.Exec(query, version, dirtyBit); err != nil {\n\t\t\tif errRollback := tx.Rollback(); errRollback != nil {\n\t\t\t\terr = errors.Join(err, errRollback)\n\t\t\t}\n\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Err: \"transaction commit failed\"}\n\t}\n\n\treturn nil\n}\n\n// Version of the current database state\nfunc (ss *SQLServer) Version() (version int, dirty bool, err error) {\n\tquery := `SELECT TOP 1 version, dirty FROM ` + ss.getMigrationTable()\n\terr = ss.conn.QueryRowContext(context.Background(), query).Scan(&version, &dirty)\n\tswitch {\n\tcase err == sql.ErrNoRows:\n\t\treturn database.NilVersion, false, nil\n\n\tcase err != nil:\n\t\t// FIXME: convert to MSSQL error\n\t\treturn 0, false, &database.Error{OrigErr: err, Query: []byte(query)}\n\n\tdefault:\n\t\treturn version, dirty, nil\n\t}\n}\n\n// Drop all tables from the database.\nfunc (ss *SQLServer) Drop() error {\n\n\t// drop all referential integrity constraints\n\tquery := `\n\tDECLARE @Sql NVARCHAR(500) DECLARE @Cursor CURSOR\n\n\tSET @Cursor = CURSOR FAST_FORWARD FOR\n\tSELECT DISTINCT sql = 'ALTER TABLE [' + tc2.TABLE_NAME + '] DROP [' + rc1.CONSTRAINT_NAME + ']'\n\tFROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc1\n\tLEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc2 ON tc2.CONSTRAINT_NAME =rc1.CONSTRAINT_NAME\n\n\tOPEN @Cursor FETCH NEXT FROM @Cursor INTO @Sql\n\n\tWHILE (@@FETCH_STATUS = 0)\n\tBEGIN\n\tExec sp_executesql @Sql\n\tFETCH NEXT FROM @Cursor INTO @Sql\n\tEND\n\n\tCLOSE @Cursor DEALLOCATE @Cursor`\n\n\tif _, err := ss.conn.ExecContext(context.Background(), query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\t// drop the tables\n\tquery = `EXEC sp_MSforeachtable 'DROP TABLE ?'`\n\tif _, err := ss.conn.ExecContext(context.Background(), query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn nil\n}\n\nfunc (ss *SQLServer) ensureVersionTable() (err error) {\n\tif err = ss.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := ss.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\tquery := `IF NOT EXISTS\n\t(SELECT *\n\t\t FROM sysobjects\n\t\tWHERE id = object_id(N'` + ss.getMigrationTable() + `')\n\t\t\tAND OBJECTPROPERTY(id, N'IsUserTable') = 1\n\t)\n\tCREATE TABLE ` + ss.getMigrationTable() + ` ( version BIGINT PRIMARY KEY NOT NULL, dirty BIT NOT NULL );`\n\n\tif _, err = ss.conn.ExecContext(context.Background(), query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn nil\n}\n\nfunc (ss *SQLServer) getMigrationTable() string {\n\treturn fmt.Sprintf(\"[%s].[%s]\", ss.config.SchemaName, ss.config.MigrationsTable)\n}\n\nfunc getMSITokenProvider(resource string) (func() (string, error), error) {\n\tmsi, err := adal.NewServicePrincipalTokenFromManagedIdentity(resource, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn func() (string, error) {\n\t\terr := msi.EnsureFresh()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\ttoken := msi.OAuthToken()\n\t\treturn token, nil\n\t}, nil\n}\n\n// The sql server resource can change across clouds so get it\n// dynamically based on the server uri.\n// ex. <server name>.database.windows.net -> https://database.windows.net\nfunc getAADResourceFromServerUri(purl *nurl.URL) string {\n\treturn fmt.Sprintf(\"%s%s\", \"https://\", strings.Join(strings.Split(purl.Hostname(), \".\")[1:], \".\"))\n}\n"
  },
  {
    "path": "database/sqlserver/sqlserver_test.go",
    "content": "package sqlserver\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\tsqldriver \"database/sql/driver\"\n\t\"fmt\"\n\t\"log\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/dhui/dktest\"\n\t\"github.com/golang-migrate/migrate/v4\"\n\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t\"github.com/golang-migrate/migrate/v4/dktesting\"\n\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nconst defaultPort = 1433\nconst saPassword = \"Root1234\"\n\nvar (\n\tsqlServerOpts = dktest.Options{\n\t\tEnv:          map[string]string{\"ACCEPT_EULA\": \"Y\", \"MSSQL_SA_PASSWORD\": saPassword, \"MSSQL_PID\": \"Express\"},\n\t\tPortRequired: true, ReadyFunc: isReady, PullTimeout: 2 * time.Minute,\n\t}\n\t// Container versions: https://mcr.microsoft.com/v2/mssql/server/tags/list\n\tspecs = []dktesting.ContainerSpec{\n\t\t{ImageName: \"mcr.microsoft.com/mssql/server:2022-latest\", Options: sqlServerOpts},\n\t\t{ImageName: \"mcr.microsoft.com/mssql/server:2019-latest\", Options: sqlServerOpts},\n\t\t// Add back support for 2017 version once the image is fixed: https://github.com/microsoft/mssql-docker/issues/899\n\t\t// {ImageName: \"mcr.microsoft.com/mssql/server:2017-latest\", Options: sqlServerOpts},\n\t}\n)\n\nfunc msConnectionString(host, port string) string {\n\treturn fmt.Sprintf(\"sqlserver://sa:%v@%v:%v?database=master\", saPassword, host, port)\n}\n\nfunc msConnectionStringMsiWithPassword(host, port string, useMsi bool) string {\n\treturn fmt.Sprintf(\"sqlserver://sa:%v@%v:%v?database=master&useMsi=%t\", saPassword, host, port, useMsi)\n}\n\nfunc msConnectionStringMsi(host, port string, useMsi bool) string {\n\treturn fmt.Sprintf(\"sqlserver://sa@%v:%v?database=master&useMsi=%t\", host, port, useMsi)\n}\n\nfunc isReady(ctx context.Context, c dktest.ContainerInfo) bool {\n\tip, port, err := c.Port(defaultPort)\n\tif err != nil {\n\t\treturn false\n\t}\n\turi := msConnectionString(ip, port)\n\tdb, err := sql.Open(\"sqlserver\", uri)\n\tif err != nil {\n\t\treturn false\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\tlog.Println(\"close error:\", err)\n\t\t}\n\t}()\n\tif err = db.PingContext(ctx); err != nil {\n\t\tswitch err {\n\t\tcase sqldriver.ErrBadConn:\n\t\t\treturn false\n\t\tdefault:\n\t\t\tfmt.Println(err)\n\t\t}\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc SkipIfUnsupportedArch(t *testing.T, c dktest.ContainerInfo) {\n\tif strings.Contains(c.ImageName, \"mssql\") && !strings.HasPrefix(runtime.GOARCH, \"amd\") {\n\t\tt.Skipf(\"Image %s is not supported on arch %s\", c.ImageName, runtime.GOARCH)\n\t}\n}\n\nfunc Test(t *testing.T) {\n\tt.Run(\"test\", test)\n\tt.Run(\"testMigrate\", testMigrate)\n\tt.Run(\"testMultiStatement\", testMultiStatement)\n\tt.Run(\"testErrorParsing\", testErrorParsing)\n\tt.Run(\"testLockWorks\", testLockWorks)\n\tt.Run(\"testMsiTrue\", testMsiTrue)\n\tt.Run(\"testOpenWithPasswordAndMSI\", testOpenWithPasswordAndMSI)\n\tt.Run(\"testMsiFalse\", testMsiFalse)\n\n\tt.Cleanup(func() {\n\t\tfor _, spec := range specs {\n\t\t\tt.Log(\"Cleaning up \", spec.ImageName)\n\t\t\tif err := spec.Cleanup(); err != nil {\n\t\t\t\tt.Error(\"Error removing \", spec.ImageName, \"error:\", err)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc test(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tSkipIfUnsupportedArch(t, c)\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := msConnectionString(ip, port)\n\t\tp := &SQLServer{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%v\", err)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\t})\n}\n\nfunc testMigrate(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tSkipIfUnsupportedArch(t, c)\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := msConnectionString(ip, port)\n\t\tp := &SQLServer{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%v\", err)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://./examples/migrations\", \"master\", d)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\t})\n}\n\nfunc testMultiStatement(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tSkipIfUnsupportedArch(t, c)\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := msConnectionString(ip, port)\n\t\tms := &SQLServer{}\n\t\td, err := ms.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\t\tif err := d.Run(strings.NewReader(\"CREATE TABLE foo (foo text); CREATE TABLE bar (bar text);\")); err != nil {\n\t\t\tt.Fatalf(\"expected err to be nil, got %v\", err)\n\t\t}\n\n\t\t// make sure second table exists\n\t\tvar exists int\n\t\tif err := d.(*SQLServer).conn.QueryRowContext(context.Background(), \"SELECT COUNT(1) FROM information_schema.tables WHERE table_name = 'bar' AND table_schema = (SELECT schema_name()) AND table_catalog = (SELECT db_name())\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif exists != 1 {\n\t\t\tt.Fatalf(\"expected table bar to exist\")\n\t\t}\n\t})\n}\n\nfunc testErrorParsing(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tSkipIfUnsupportedArch(t, c)\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := msConnectionString(ip, port)\n\n\t\tp := &SQLServer{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\twantErr := `migration failed: Unknown object type 'TABLEE' used in a CREATE, DROP, or ALTER statement. in line 1:` +\n\t\t\t` CREATE TABLE foo (foo text); CREATE TABLEE bar (bar text); (details: mssql: Unknown object type ` +\n\t\t\t`'TABLEE' used in a CREATE, DROP, or ALTER statement.)`\n\t\tif err := d.Run(strings.NewReader(\"CREATE TABLE foo (foo text); CREATE TABLEE bar (bar text);\")); err == nil {\n\t\t\tt.Fatal(\"expected err but got nil\")\n\t\t} else if err.Error() != wantErr {\n\t\t\tt.Fatalf(\"expected '%s' but got '%s'\", wantErr, err.Error())\n\t\t}\n\t})\n}\n\nfunc testLockWorks(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tSkipIfUnsupportedArch(t, c)\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := fmt.Sprintf(\"sqlserver://sa:%v@%v:%v?master\", saPassword, ip, port)\n\t\tp := &SQLServer{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%v\", err)\n\t\t}\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\n\t\tms := d.(*SQLServer)\n\n\t\terr = ms.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\terr = ms.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// make sure the 2nd lock works (RELEASE_LOCK is very finicky)\n\t\terr = ms.Lock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\terr = ms.Unlock()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc testMsiTrue(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tSkipIfUnsupportedArch(t, c)\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := msConnectionStringMsi(ip, port, true)\n\t\tp := &SQLServer{}\n\t\t_, err = p.Open(addr)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"MSI should fail when not running in an Azure context.\")\n\t\t}\n\t})\n}\n\nfunc testOpenWithPasswordAndMSI(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tSkipIfUnsupportedArch(t, c)\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := msConnectionStringMsiWithPassword(ip, port, true)\n\t\tp := &SQLServer{}\n\t\t_, err = p.Open(addr)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Open should fail when both password and useMsi=true are passed.\")\n\t\t}\n\n\t\taddr = msConnectionStringMsiWithPassword(ip, port, false)\n\t\tp = &SQLServer{}\n\t\td, err := p.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif err := d.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\t})\n}\n\nfunc testMsiFalse(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {\n\t\tSkipIfUnsupportedArch(t, c)\n\t\tip, port, err := c.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := msConnectionStringMsi(ip, port, false)\n\t\tp := &SQLServer{}\n\t\t_, err = p.Open(addr)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Open should fail since no password was passed and useMsi is false.\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "database/stub/stub.go",
    "content": "package stub\n\nimport (\n\t\"io\"\n\t\"reflect\"\n\t\"sync/atomic\"\n\n\t\"github.com/golang-migrate/migrate/v4/database\"\n)\n\nfunc init() {\n\tdatabase.Register(\"stub\", &Stub{})\n}\n\ntype Stub struct {\n\tUrl               string\n\tInstance          interface{}\n\tCurrentVersion    int\n\tMigrationSequence []string\n\tLastRunMigration  []byte // todo: make []string\n\tIsDirty           bool\n\tisLocked          atomic.Bool\n\n\tConfig *Config\n}\n\nfunc (s *Stub) Open(url string) (database.Driver, error) {\n\treturn &Stub{\n\t\tUrl:               url,\n\t\tCurrentVersion:    database.NilVersion,\n\t\tMigrationSequence: make([]string, 0),\n\t\tConfig:            &Config{},\n\t}, nil\n}\n\ntype Config struct{}\n\nfunc WithInstance(instance interface{}, config *Config) (database.Driver, error) {\n\treturn &Stub{\n\t\tInstance:          instance,\n\t\tCurrentVersion:    database.NilVersion,\n\t\tMigrationSequence: make([]string, 0),\n\t\tConfig:            config,\n\t}, nil\n}\n\nfunc (s *Stub) Close() error {\n\treturn nil\n}\n\nfunc (s *Stub) Lock() error {\n\tif !s.isLocked.CompareAndSwap(false, true) {\n\t\treturn database.ErrLocked\n\t}\n\treturn nil\n}\n\nfunc (s *Stub) Unlock() error {\n\tif !s.isLocked.CompareAndSwap(true, false) {\n\t\treturn database.ErrNotLocked\n\t}\n\treturn nil\n}\n\nfunc (s *Stub) Run(migration io.Reader) error {\n\tm, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.LastRunMigration = m\n\ts.MigrationSequence = append(s.MigrationSequence, string(m[:]))\n\treturn nil\n}\n\nfunc (s *Stub) SetVersion(version int, state bool) error {\n\ts.CurrentVersion = version\n\ts.IsDirty = state\n\treturn nil\n}\n\nfunc (s *Stub) Version() (version int, dirty bool, err error) {\n\treturn s.CurrentVersion, s.IsDirty, nil\n}\n\nconst DROP = \"DROP\"\n\nfunc (s *Stub) Drop() error {\n\ts.CurrentVersion = database.NilVersion\n\ts.LastRunMigration = nil\n\ts.MigrationSequence = append(s.MigrationSequence, DROP)\n\treturn nil\n}\n\nfunc (s *Stub) EqualSequence(seq []string) bool {\n\treturn reflect.DeepEqual(seq, s.MigrationSequence)\n}\n"
  },
  {
    "path": "database/stub/stub_test.go",
    "content": "package stub\n\nimport (\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/source\"\n\t\"github.com/golang-migrate/migrate/v4/source/stub\"\n\t\"testing\"\n\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n)\n\nfunc Test(t *testing.T) {\n\ts := &Stub{}\n\td, err := s.Open(\"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdt.Test(t, d, []byte(\"/* foobar migration */\"))\n}\n\nfunc TestMigrate(t *testing.T) {\n\ts := &Stub{}\n\td, err := s.Open(\"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tstubMigrations := source.NewMigrations()\n\tstubMigrations.Append(&source.Migration{Version: 1, Direction: source.Up, Identifier: \"CREATE 1\"})\n\tstubMigrations.Append(&source.Migration{Version: 1, Direction: source.Down, Identifier: \"DROP 1\"})\n\tsrc := &stub.Stub{}\n\tsrcDrv, err := src.Open(\"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tsrcDrv.(*stub.Stub).Migrations = stubMigrations\n\tm, err := migrate.NewWithInstance(\"stub\", srcDrv, \"\", d)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdt.TestMigrate(t, m)\n}\n"
  },
  {
    "path": "database/testing/migrate_testing.go",
    "content": "// Package testing has the database tests.\n// All database drivers must pass the Test function.\n// This lives in it's own package so it stays a test dependency.\npackage testing\n\nimport (\n\t\"testing\"\n)\n\nimport (\n\t\"github.com/golang-migrate/migrate/v4\"\n)\n\n// TestMigrate runs integration-tests between the Migrate layer and database implementations.\nfunc TestMigrate(t *testing.T, m *migrate.Migrate) {\n\tTestMigrateUp(t, m)\n\tTestMigrateDrop(t, m)\n}\n\n// Regression test for preventing a regression for #164 https://github.com/golang-migrate/migrate/pull/173\n// Similar to TestDrop(), but tests the dropping mechanism through the Migrate logic instead, to check for\n// double-locking during the Drop logic.\nfunc TestMigrateDrop(t *testing.T, m *migrate.Migrate) {\n\tif err := m.Drop(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestMigrateUp(t *testing.T, m *migrate.Migrate) {\n\tt.Log(\"UP\")\n\tif err := m.Up(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "database/testing/testing.go",
    "content": "// Package testing has the database tests.\n// All database drivers must pass the Test function.\n// This lives in it's own package so it stays a test dependency.\npackage testing\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/golang-migrate/migrate/v4/database\"\n)\n\n// Test runs tests against database implementations.\nfunc Test(t *testing.T, d database.Driver, migration []byte) {\n\tif migration == nil {\n\t\tt.Fatal(\"test must provide migration reader\")\n\t}\n\n\tTestNilVersion(t, d) // test first\n\tTestLockAndUnlock(t, d)\n\tTestRun(t, d, bytes.NewReader(migration))\n\tTestSetVersion(t, d) // also tests Version()\n\t// Drop breaks the driver, so test it last.\n\tTestDrop(t, d)\n}\n\nfunc TestNilVersion(t *testing.T, d database.Driver) {\n\tv, _, err := d.Version()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif v != database.NilVersion {\n\t\tt.Fatalf(\"Version: expected version to be NilVersion (-1), got %v\", v)\n\t}\n}\n\nfunc TestLockAndUnlock(t *testing.T, d database.Driver) {\n\t// add a timeout, in case there is a deadlock\n\tdone := make(chan struct{})\n\terrs := make(chan error)\n\n\tgo func() {\n\t\ttimeout := time.After(15 * time.Second)\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\t\treturn\n\t\t\tcase <-timeout:\n\t\t\t\terrs <- fmt.Errorf(\"timeout after 15 seconds, looks like a deadlock in Lock/UnLock\\n%#v\", d)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\t// run the locking test ...\n\tgo func() {\n\t\tif err := d.Lock(); err != nil {\n\t\t\terrs <- err\n\t\t\treturn\n\t\t}\n\n\t\t// try to acquire lock again\n\t\tif err := d.Lock(); err == nil {\n\t\t\terrs <- errors.New(\"lock: expected err not to be nil\")\n\t\t\treturn\n\t\t}\n\n\t\t// unlock\n\t\tif err := d.Unlock(); err != nil {\n\t\t\terrs <- err\n\t\t\treturn\n\t\t}\n\n\t\t// try to lock\n\t\tif err := d.Lock(); err != nil {\n\t\t\terrs <- err\n\t\t\treturn\n\t\t}\n\t\tif err := d.Unlock(); err != nil {\n\t\t\terrs <- err\n\t\t\treturn\n\t\t}\n\t\t// notify everyone\n\t\tclose(done)\n\t}()\n\n\t// wait for done or any error\n\tfor {\n\t\tselect {\n\t\tcase <-done:\n\t\t\treturn\n\t\tcase err := <-errs:\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc TestRun(t *testing.T, d database.Driver, migration io.Reader) {\n\tif migration == nil {\n\t\tt.Fatal(\"migration can't be nil\")\n\t}\n\n\tif err := d.Run(migration); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestDrop(t *testing.T, d database.Driver) {\n\tif err := d.Drop(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestSetVersion(t *testing.T, d database.Driver) {\n\ttestCases := []struct {\n\t\tname            string\n\t\tversion         int\n\t\tdirty           bool\n\t\texpectedErr     error\n\t\texpectedReadErr error\n\t\texpectedVersion int\n\t\texpectedDirty   bool\n\t}{\n\t\t{name: \"set 1 dirty\", version: 1, dirty: true, expectedErr: nil, expectedReadErr: nil, expectedVersion: 1, expectedDirty: true},\n\t\t{name: \"re-set 1 dirty\", version: 1, dirty: true, expectedErr: nil, expectedReadErr: nil, expectedVersion: 1, expectedDirty: true},\n\t\t{name: \"set 2 clean\", version: 2, dirty: false, expectedErr: nil, expectedReadErr: nil, expectedVersion: 2, expectedDirty: false},\n\t\t{name: \"re-set 2 clean\", version: 2, dirty: false, expectedErr: nil, expectedReadErr: nil, expectedVersion: 2, expectedDirty: false},\n\t\t{name: \"last migration dirty\", version: database.NilVersion, dirty: true, expectedErr: nil, expectedReadErr: nil, expectedVersion: database.NilVersion, expectedDirty: true},\n\t\t{name: \"last migration clean\", version: database.NilVersion, dirty: false, expectedErr: nil, expectedReadErr: nil, expectedVersion: database.NilVersion, expectedDirty: false},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\terr := d.SetVersion(tc.version, tc.dirty)\n\t\t\tif err != tc.expectedErr {\n\t\t\t\tt.Fatal(\"Got unexpected error:\", err, \"!=\", tc.expectedErr)\n\t\t\t}\n\t\t\tv, dirty, readErr := d.Version()\n\t\t\tif readErr != tc.expectedReadErr {\n\t\t\t\tt.Fatal(\"Got unexpected error:\", readErr, \"!=\", tc.expectedReadErr)\n\t\t\t}\n\t\t\tif v != tc.expectedVersion {\n\t\t\t\tt.Error(\"Got unexpected version:\", v, \"!=\", tc.expectedVersion)\n\t\t\t}\n\t\t\tif dirty != tc.expectedDirty {\n\t\t\t\tt.Error(\"Got unexpected dirty value:\", dirty, \"!=\", tc.dirty)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "database/util.go",
    "content": "package database\n\nimport (\n\t\"fmt\"\n\t\"hash/crc32\"\n\t\"strings\"\n\t\"sync/atomic\"\n)\n\nconst advisoryLockIDSalt uint = 1486364155\n\n// GenerateAdvisoryLockId inspired by rails migrations, see https://goo.gl/8o9bCT\nfunc GenerateAdvisoryLockId(databaseName string, additionalNames ...string) (string, error) { // nolint: golint\n\tif len(additionalNames) > 0 {\n\t\tdatabaseName = strings.Join(append(additionalNames, databaseName), \"\\x00\")\n\t}\n\tsum := crc32.ChecksumIEEE([]byte(databaseName))\n\tsum = sum * uint32(advisoryLockIDSalt)\n\treturn fmt.Sprint(sum), nil\n}\n\n// CasRestoreOnErr CAS wrapper to automatically restore the lock state on error\nfunc CasRestoreOnErr(lock *atomic.Bool, o, n bool, casErr error, f func() error) error {\n\tif !lock.CompareAndSwap(o, n) {\n\t\treturn casErr\n\t}\n\tif err := f(); err != nil {\n\t\t// Automatically unlock/lock on error\n\t\tlock.Store(o)\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "database/util_test.go",
    "content": "package database\n\nimport (\n\t\"errors\"\n\t\"sync/atomic\"\n\t\"testing\"\n)\n\nfunc TestGenerateAdvisoryLockId(t *testing.T) {\n\ttestcases := []struct {\n\t\tdbname     string\n\t\tadditional []string\n\t\texpectedID string // empty string signifies that an error is expected\n\t}{\n\t\t{\n\t\t\tdbname:     \"database_name\",\n\t\t\texpectedID: \"1764327054\",\n\t\t},\n\t\t{\n\t\t\tdbname:     \"database_name\",\n\t\t\tadditional: []string{\"schema_name_1\"},\n\t\t\texpectedID: \"2453313553\",\n\t\t},\n\t\t{\n\t\t\tdbname:     \"database_name\",\n\t\t\tadditional: []string{\"schema_name_2\"},\n\t\t\texpectedID: \"235207038\",\n\t\t},\n\t\t{\n\t\t\tdbname:     \"database_name\",\n\t\t\tadditional: []string{\"schema_name_1\", \"schema_name_2\"},\n\t\t\texpectedID: \"3743845847\",\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.dbname, func(t *testing.T) {\n\t\t\tif id, err := GenerateAdvisoryLockId(tc.dbname, tc.additional...); err == nil {\n\t\t\t\tif id != tc.expectedID {\n\t\t\t\t\tt.Error(\"Generated incorrect ID:\", id, \"!=\", tc.expectedID)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif tc.expectedID != \"\" {\n\t\t\t\t\tt.Error(\"Got unexpected error:\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCasRestoreOnErr(t *testing.T) {\n\tcasErr := errors.New(\"test lock CAS failure\")\n\tfErr := errors.New(\"test callback error\")\n\n\ttestcases := []struct {\n\t\tname        string\n\t\tlock        bool\n\t\tfrom        bool\n\t\tto          bool\n\t\texpectLock  bool\n\t\tfErr        error\n\t\texpectError error\n\t}{\n\t\t{\n\t\t\tname:        \"Test positive CAS lock\",\n\t\t\tlock:        false,\n\t\t\tfrom:        false,\n\t\t\tto:          true,\n\t\t\texpectLock:  true,\n\t\t\tfErr:        nil,\n\t\t\texpectError: nil,\n\t\t},\n\t\t{\n\t\t\tname:        \"Test negative CAS lock\",\n\t\t\tlock:        true,\n\t\t\tfrom:        false,\n\t\t\tto:          true,\n\t\t\texpectLock:  true,\n\t\t\tfErr:        nil,\n\t\t\texpectError: casErr,\n\t\t},\n\t\t{\n\t\t\tname:        \"Test negative with callback lock\",\n\t\t\tlock:        false,\n\t\t\tfrom:        false,\n\t\t\tto:          true,\n\t\t\texpectLock:  false,\n\t\t\tfErr:        fErr,\n\t\t\texpectError: fErr,\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tvar lock atomic.Bool\n\t\t\tlock.Store(tc.lock)\n\t\t\tif err := CasRestoreOnErr(&lock, tc.from, tc.to, casErr, func() error {\n\t\t\t\treturn tc.fErr\n\t\t\t}); err != tc.expectError {\n\t\t\t\tt.Error(\"Incorrect error value returned\")\n\t\t\t}\n\n\t\t\tif lock.Load() != tc.expectLock {\n\t\t\t\tt.Error(\"Incorrect state of lock\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "database/yugabytedb/README.md",
    "content": "# yugabytedb\n\n`yugabytedb://user:password@host:port/dbname?query` (`yugabyte://`, and `ysql://` work, too)\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |\n| `x-lock-table` | `LockTable` | Name of the table which maintains the migration lock |\n| `x-force-lock` | `ForceLock` | Force lock acquisition to fix faulty migrations which may not have released the schema lock (Boolean, default is `false`) |\n| `x-max-retries` | `MaxRetries` | How many times retry queries on retryable errors (40001, 40P01, 08006, XX000). Default is 10 |\n| `x-max-retry-interval` | `MaxRetryInterval` | Interval between retries increases exponentially. This option specifies maximum duration between retries. Default is 15s |\n| `x-max-retry-elapsed-time` | `MaxRetryElapsedTime` | Total retries timeout. Default is 30s |\n| `dbname` | `DatabaseName` | The name of the database to connect to |\n| `user` | | The user to sign in as |\n| `password` | | The user's password |\n| `host` | | The host to connect to. Values that start with / are for unix domain sockets. (default is localhost) |\n| `port` | | The port to bind to. (default is 5432) |\n| `connect_timeout` | | Maximum wait for connection, in seconds. Zero or not specified means wait indefinitely. |\n| `sslcert` | | Cert file location. The file must contain PEM encoded data. |\n| `sslkey` | | Key file location. The file must contain PEM encoded data. |\n| `sslrootcert` | | The location of the root certificate file. The file must contain PEM encoded data. |\n| `sslmode` | | Whether or not to use SSL (disable\\|require\\|verify-ca\\|verify-full) |\n"
  },
  {
    "path": "database/yugabytedb/examples/migrations/1085649617_create_users_table.down.sql",
    "content": "DROP TABLE IF EXISTS users;\n"
  },
  {
    "path": "database/yugabytedb/examples/migrations/1085649617_create_users_table.up.sql",
    "content": "CREATE TABLE users (\n  user_id integer unique,\n  name    varchar(40),\n  email   varchar(40)\n);\n"
  },
  {
    "path": "database/yugabytedb/examples/migrations/1185749658_add_city_to_users.down.sql",
    "content": "ALTER TABLE users DROP COLUMN IF EXISTS city;\n"
  },
  {
    "path": "database/yugabytedb/examples/migrations/1185749658_add_city_to_users.up.sql",
    "content": "ALTER TABLE users ADD COLUMN city varchar(100);\n\n\n"
  },
  {
    "path": "database/yugabytedb/examples/migrations/1285849751_add_index_on_user_emails.down.sql",
    "content": "DROP INDEX IF EXISTS users_email_index;\n"
  },
  {
    "path": "database/yugabytedb/examples/migrations/1285849751_add_index_on_user_emails.up.sql",
    "content": "CREATE UNIQUE INDEX users_email_index ON users (email);\n\n-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/yugabytedb/examples/migrations/1385949617_create_books_table.down.sql",
    "content": "DROP TABLE IF EXISTS books;\n"
  },
  {
    "path": "database/yugabytedb/examples/migrations/1385949617_create_books_table.up.sql",
    "content": "CREATE TABLE books (\n  user_id integer,\n  name    varchar(40),\n  author  varchar(40)\n);\n"
  },
  {
    "path": "database/yugabytedb/examples/migrations/1485949617_create_movies_table.down.sql",
    "content": "DROP TABLE IF EXISTS movies;\n"
  },
  {
    "path": "database/yugabytedb/examples/migrations/1485949617_create_movies_table.up.sql",
    "content": "CREATE TABLE movies (\n  user_id   integer,\n  name      varchar(40),\n  director  varchar(40)\n);\n"
  },
  {
    "path": "database/yugabytedb/examples/migrations/1585849751_just_a_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/yugabytedb/examples/migrations/1685849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/yugabytedb/examples/migrations/1785849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/yugabytedb/examples/migrations/1885849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "database/yugabytedb/yugabytedb.go",
    "content": "package yugabytedb\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"io\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff/v4\"\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t\"github.com/jackc/pgconn\"\n\t\"github.com/jackc/pgerrcode\"\n\t\"github.com/lib/pq\"\n)\n\nconst (\n\tDefaultMaxRetryInterval    = time.Second * 15\n\tDefaultMaxRetryElapsedTime = time.Second * 30\n\tDefaultMaxRetries          = 10\n\tDefaultMigrationsTable     = \"migrations\"\n\tDefaultLockTable           = \"migrations_locks\"\n)\n\nvar (\n\tErrNilConfig          = errors.New(\"no config\")\n\tErrNoDatabaseName     = errors.New(\"no database name\")\n\tErrMaxRetriesExceeded = errors.New(\"max retries exceeded\")\n)\n\nfunc init() {\n\tdb := YugabyteDB{}\n\tdatabase.Register(\"yugabyte\", &db)\n\tdatabase.Register(\"yugabytedb\", &db)\n\tdatabase.Register(\"ysql\", &db)\n}\n\ntype Config struct {\n\tMigrationsTable     string\n\tLockTable           string\n\tForceLock           bool\n\tDatabaseName        string\n\tMaxRetryInterval    time.Duration\n\tMaxRetryElapsedTime time.Duration\n\tMaxRetries          int\n}\n\ntype YugabyteDB struct {\n\tdb       *sql.DB\n\tisLocked atomic.Bool\n\n\t// Open and WithInstance need to guarantee that config is never nil\n\tconfig *Config\n}\n\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}\n\n\tif err := instance.Ping(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif config.DatabaseName == \"\" {\n\t\tquery := `SELECT current_database()`\n\t\tvar databaseName string\n\t\tif err := instance.QueryRow(query).Scan(&databaseName); err != nil {\n\t\t\treturn nil, &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t}\n\n\t\tif len(databaseName) == 0 {\n\t\t\treturn nil, ErrNoDatabaseName\n\t\t}\n\n\t\tconfig.DatabaseName = databaseName\n\t}\n\n\tif len(config.MigrationsTable) == 0 {\n\t\tconfig.MigrationsTable = DefaultMigrationsTable\n\t}\n\n\tif len(config.LockTable) == 0 {\n\t\tconfig.LockTable = DefaultLockTable\n\t}\n\n\tif config.MaxRetryInterval == 0 {\n\t\tconfig.MaxRetryInterval = DefaultMaxRetryInterval\n\t}\n\n\tif config.MaxRetryElapsedTime == 0 {\n\t\tconfig.MaxRetryElapsedTime = DefaultMaxRetryElapsedTime\n\t}\n\n\tif config.MaxRetries == 0 {\n\t\tconfig.MaxRetries = DefaultMaxRetries\n\t}\n\n\tpx := &YugabyteDB{\n\t\tdb:     instance,\n\t\tconfig: config,\n\t}\n\n\t// ensureVersionTable is a locking operation, so we need to ensureLockTable before we ensureVersionTable.\n\tif err := px.ensureLockTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := px.ensureVersionTable(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\nfunc (c *YugabyteDB) Open(dbURL string) (database.Driver, error) {\n\tpurl, err := url.Parse(dbURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// As YugabyteDB uses the postgres protocol, and 'postgres' is already a registered database, we need to replace the\n\t// connect prefix, with the actual protocol, so that the library can differentiate between the implementations\n\tre := regexp.MustCompile(\"^(yugabyte(db)?|ysql)\")\n\tconnectString := re.ReplaceAllString(migrate.FilterCustomQuery(purl).String(), \"postgres\")\n\n\tdb, err := sql.Open(\"postgres\", connectString)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmigrationsTable := purl.Query().Get(\"x-migrations-table\")\n\tif len(migrationsTable) == 0 {\n\t\tmigrationsTable = DefaultMigrationsTable\n\t}\n\n\tlockTable := purl.Query().Get(\"x-lock-table\")\n\tif len(lockTable) == 0 {\n\t\tlockTable = DefaultLockTable\n\t}\n\n\tforceLockQuery := purl.Query().Get(\"x-force-lock\")\n\tforceLock, err := strconv.ParseBool(forceLockQuery)\n\tif err != nil {\n\t\tforceLock = false\n\t}\n\n\tmaxIntervalStr := purl.Query().Get(\"x-max-retry-interval\")\n\tmaxInterval, err := time.ParseDuration(maxIntervalStr)\n\tif err != nil {\n\t\tmaxInterval = DefaultMaxRetryInterval\n\t}\n\n\tmaxElapsedTimeStr := purl.Query().Get(\"x-max-retry-elapsed-time\")\n\tmaxElapsedTime, err := time.ParseDuration(maxElapsedTimeStr)\n\tif err != nil {\n\t\tmaxElapsedTime = DefaultMaxRetryElapsedTime\n\t}\n\n\tmaxRetriesStr := purl.Query().Get(\"x-max-retries\")\n\tmaxRetries, err := strconv.Atoi(maxRetriesStr)\n\tif err != nil {\n\t\tmaxRetries = DefaultMaxRetries\n\t}\n\n\tpx, err := WithInstance(db, &Config{\n\t\tDatabaseName:        purl.Path,\n\t\tMigrationsTable:     migrationsTable,\n\t\tLockTable:           lockTable,\n\t\tForceLock:           forceLock,\n\t\tMaxRetryInterval:    maxInterval,\n\t\tMaxRetryElapsedTime: maxElapsedTime,\n\t\tMaxRetries:          maxRetries,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn px, nil\n}\n\nfunc (c *YugabyteDB) Close() error {\n\treturn c.db.Close()\n}\n\n// Locking is done manually with a separate lock table. Implementing advisory locks in YugabyteDB is being discussed\n// See: https://github.com/yugabyte/yugabyte-db/issues/3642\nfunc (c *YugabyteDB) Lock() error {\n\treturn database.CasRestoreOnErr(&c.isLocked, false, true, database.ErrLocked, func() (err error) {\n\t\treturn c.doTxWithRetry(context.Background(), &sql.TxOptions{Isolation: sql.LevelSerializable}, func(tx *sql.Tx) (err error) {\n\t\t\taid, err := database.GenerateAdvisoryLockId(c.config.DatabaseName)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tquery := \"SELECT * FROM \" + c.config.LockTable + \" WHERE lock_id = $1\"\n\t\t\trows, err := tx.Query(query, aid)\n\t\t\tif err != nil {\n\t\t\t\treturn database.Error{OrigErr: err, Err: \"failed to fetch migration lock\", Query: []byte(query)}\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\tif errClose := rows.Close(); errClose != nil {\n\t\t\t\t\terr = errors.Join(err, errClose)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// If row exists at all, lock is present\n\t\t\tlocked := rows.Next()\n\t\t\tif locked && !c.config.ForceLock {\n\t\t\t\treturn database.ErrLocked\n\t\t\t}\n\n\t\t\tquery = \"INSERT INTO \" + c.config.LockTable + \" (lock_id) VALUES ($1)\"\n\t\t\tif _, err := tx.Exec(query, aid); err != nil {\n\t\t\t\treturn database.Error{OrigErr: err, Err: \"failed to set migration lock\", Query: []byte(query)}\n\t\t\t}\n\n\t\t\treturn nil\n\t\t})\n\t})\n}\n\n// Locking is done manually with a separate lock table. Implementing advisory locks in YugabyteDB is being discussed\n// See: https://github.com/yugabyte/yugabyte-db/issues/3642\nfunc (c *YugabyteDB) Unlock() error {\n\treturn database.CasRestoreOnErr(&c.isLocked, true, false, database.ErrNotLocked, func() (err error) {\n\t\taid, err := database.GenerateAdvisoryLockId(c.config.DatabaseName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// In the event of an implementation (non-migration) error, it is possible for the lock to not be released. Until\n\t\t// a better locking mechanism is added, a manual purging of the lock table may be required in such circumstances\n\t\tquery := \"DELETE FROM \" + c.config.LockTable + \" WHERE lock_id = $1\"\n\t\tif _, err := c.db.Exec(query, aid); err != nil {\n\t\t\tif e, ok := err.(*pq.Error); ok {\n\t\t\t\t// 42P01 is \"UndefinedTableError\" in YugabyteDB\n\t\t\t\t// https://github.com/yugabyte/yugabyte-db/blob/9c6b8e6beb56eed8eeb357178c0c6b837eb49896/src/postgres/src/backend/utils/errcodes.txt#L366\n\t\t\t\tif e.Code == \"42P01\" {\n\t\t\t\t\t// On drops, the lock table is fully removed; This is fine, and is a valid \"unlocked\" state for the schema\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn database.Error{OrigErr: err, Err: \"failed to release migration lock\", Query: []byte(query)}\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc (c *YugabyteDB) Run(migration io.Reader) error {\n\tmigr, err := io.ReadAll(migration)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// run migration\n\tquery := string(migr[:])\n\tif _, err := c.db.Exec(query); err != nil {\n\t\treturn database.Error{OrigErr: err, Err: \"migration failed\", Query: migr}\n\t}\n\n\treturn nil\n}\n\nfunc (c *YugabyteDB) SetVersion(version int, dirty bool) error {\n\treturn c.doTxWithRetry(context.Background(), &sql.TxOptions{Isolation: sql.LevelSerializable}, func(tx *sql.Tx) error {\n\t\tif _, err := tx.Exec(`DELETE FROM \"` + c.config.MigrationsTable + `\"`); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Also re-write the schema version for nil dirty versions to prevent\n\t\t// empty schema version for failed down migration on the first migration\n\t\t// See: https://github.com/golang-migrate/migrate/issues/330\n\t\tif version >= 0 || (version == database.NilVersion && dirty) {\n\t\t\tif _, err := tx.Exec(`INSERT INTO \"`+c.config.MigrationsTable+`\" (version, dirty) VALUES ($1, $2)`, version, dirty); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc (c *YugabyteDB) Version() (version int, dirty bool, err error) {\n\tquery := `SELECT version, dirty FROM \"` + c.config.MigrationsTable + `\" LIMIT 1`\n\terr = c.db.QueryRow(query).Scan(&version, &dirty)\n\n\tswitch {\n\tcase err == sql.ErrNoRows:\n\t\treturn database.NilVersion, false, nil\n\n\tcase err != nil:\n\t\tif e, ok := err.(*pq.Error); ok {\n\t\t\t// 42P01 is \"UndefinedTableError\" in YugabyteDB\n\t\t\t// https://github.com/yugabyte/yugabyte-db/blob/9c6b8e6beb56eed8eeb357178c0c6b837eb49896/src/postgres/src/backend/utils/errcodes.txt#L366\n\t\t\tif e.Code == \"42P01\" {\n\t\t\t\treturn database.NilVersion, false, nil\n\t\t\t}\n\t\t}\n\t\treturn 0, false, &database.Error{OrigErr: err, Query: []byte(query)}\n\n\tdefault:\n\t\treturn version, dirty, nil\n\t}\n}\n\nfunc (c *YugabyteDB) Drop() (err error) {\n\tquery := `SELECT table_name FROM information_schema.tables WHERE table_schema=(SELECT current_schema()) AND table_type='BASE TABLE'`\n\ttables, err := c.db.Query(query)\n\tif err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tdefer func() {\n\t\tif errClose := tables.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\t// delete one table after another\n\ttableNames := make([]string, 0)\n\tfor tables.Next() {\n\t\tvar tableName string\n\t\tif err := tables.Scan(&tableName); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(tableName) > 0 {\n\t\t\ttableNames = append(tableNames, tableName)\n\t\t}\n\t}\n\tif err := tables.Err(); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\tif len(tableNames) > 0 {\n\t\tfor _, t := range tableNames {\n\t\t\tquery = `DROP TABLE IF EXISTS ` + t + ` CASCADE`\n\t\t\tif _, err := c.db.Exec(query); err != nil {\n\t\t\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ensureVersionTable checks if versions table exists and, if not, creates it.\n// Note that this function locks the database\nfunc (c *YugabyteDB) ensureVersionTable() (err error) {\n\tif err = c.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif e := c.Unlock(); e != nil {\n\t\t\terr = errors.Join(err, e)\n\t\t}\n\t}()\n\n\t// check if migration table exists\n\tvar count int\n\tquery := `SELECT COUNT(1) FROM information_schema.tables WHERE table_name = $1 AND table_schema = (SELECT current_schema()) LIMIT 1`\n\tif err := c.db.QueryRow(query, c.config.MigrationsTable).Scan(&count); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tif count == 1 {\n\t\treturn nil\n\t}\n\n\t// if not, create the empty migration table\n\tquery = `CREATE TABLE \"` + c.config.MigrationsTable + `\" (version INT NOT NULL PRIMARY KEY, dirty BOOL NOT NULL)`\n\tif _, err := c.db.Exec(query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\treturn nil\n}\n\nfunc (c *YugabyteDB) ensureLockTable() error {\n\t// check if lock table exists\n\tvar count int\n\tquery := `SELECT COUNT(1) FROM information_schema.tables WHERE table_name = $1 AND table_schema = (SELECT current_schema()) LIMIT 1`\n\tif err := c.db.QueryRow(query, c.config.LockTable).Scan(&count); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\tif count == 1 {\n\t\treturn nil\n\t}\n\n\t// if not, create the empty lock table\n\tquery = `CREATE TABLE \"` + c.config.LockTable + `\" (lock_id TEXT NOT NULL PRIMARY KEY)`\n\tif _, err := c.db.Exec(query); err != nil {\n\t\treturn &database.Error{OrigErr: err, Query: []byte(query)}\n\t}\n\n\treturn nil\n}\n\nfunc (c *YugabyteDB) doTxWithRetry(\n\tctx context.Context,\n\ttxOpts *sql.TxOptions,\n\tfn func(tx *sql.Tx) error,\n) error {\n\tbackOff := c.newBackoff(ctx)\n\n\treturn backoff.Retry(func() error {\n\t\ttx, err := c.db.BeginTx(ctx, txOpts)\n\t\tif err != nil {\n\t\t\treturn backoff.Permanent(err)\n\t\t}\n\n\t\t// If we've tried to commit the transaction Rollback just returns sql.ErrTxDone.\n\t\tdefer func() {\n\t\t\t_ = tx.Rollback()\n\t\t}()\n\n\t\tif err := fn(tx); err != nil {\n\t\t\tif errIsRetryable(err) {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\treturn backoff.Permanent(err)\n\t\t}\n\n\t\tif err := tx.Commit(); err != nil {\n\t\t\tif errIsRetryable(err) {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\treturn backoff.Permanent(err)\n\t\t}\n\n\t\treturn nil\n\t}, backOff)\n}\n\nfunc (c *YugabyteDB) newBackoff(ctx context.Context) backoff.BackOff {\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\n\tretrier := backoff.WithMaxRetries(backoff.WithContext(&backoff.ExponentialBackOff{\n\t\tInitialInterval:     backoff.DefaultInitialInterval,\n\t\tRandomizationFactor: backoff.DefaultRandomizationFactor,\n\t\tMultiplier:          backoff.DefaultMultiplier,\n\t\tMaxInterval:         c.config.MaxRetryInterval,\n\t\tMaxElapsedTime:      c.config.MaxRetryElapsedTime,\n\t\tStop:                backoff.Stop,\n\t\tClock:               backoff.SystemClock,\n\t}, ctx), uint64(c.config.MaxRetries))\n\n\tretrier.Reset()\n\n\treturn retrier\n}\n\nfunc errIsRetryable(err error) bool {\n\tvar pgErr *pgconn.PgError\n\tif !errors.As(err, &pgErr) {\n\t\treturn false\n\t}\n\n\t// Assume that it's safe to retry 08006 and XX000 because we check for lock existence\n\t// before creating and lock ID is primary key. Version field in migrations table is primary key too\n\t// and delete all versions is an idempotent operation.\n\treturn pgErr.Code == pgerrcode.SerializationFailure || // optimistic locking conflict\n\t\tpgErr.Code == pgerrcode.DeadlockDetected ||\n\t\tpgErr.Code == pgerrcode.ConnectionFailure || // node down, need to reconnect\n\t\tpgErr.Code == pgerrcode.InternalError // may happen during HA\n}\n"
  },
  {
    "path": "database/yugabytedb/yugabytedb_test.go",
    "content": "package yugabytedb\n\n// error codes https://github.com/lib/pq/blob/master/error.go\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"log\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/dhui/dktest\"\n\t\"github.com/golang-migrate/migrate/v4\"\n\n\t_ \"github.com/lib/pq\"\n\n\tdt \"github.com/golang-migrate/migrate/v4/database/testing\"\n\t\"github.com/golang-migrate/migrate/v4/dktesting\"\n\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nconst defaultPort = 5433\n\nvar (\n\topts = dktest.Options{\n\t\tCmd:          []string{\"bin/yugabyted\", \"start\", \"--daemon=false\"},\n\t\tPortRequired: true,\n\t\tReadyFunc:    isReady,\n\t\tTimeout:      time.Duration(60) * time.Second,\n\t}\n\t// Released versions: https://docs.yugabyte.com/preview/releases/release-notes/\n\tspecs = []dktesting.ContainerSpec{\n\t\t{ImageName: \"yugabytedb/yugabyte:2.14.15.0-b57\", Options: opts},\n\t\t{ImageName: \"yugabytedb/yugabyte:2.20.2.1-b3\", Options: opts},\n\t}\n)\n\nfunc isReady(ctx context.Context, c dktest.ContainerInfo) bool {\n\tip, port, err := c.Port(defaultPort)\n\tif err != nil {\n\t\tlog.Println(\"port error:\", err)\n\t\treturn false\n\t}\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"postgres://yugabyte:yugabyte@%v:%v?sslmode=disable\", ip, port))\n\tif err != nil {\n\t\tlog.Println(\"open error:\", err)\n\t\treturn false\n\t}\n\tif err := db.PingContext(ctx); err != nil {\n\t\tlog.Println(\"ping error:\", err)\n\t\treturn false\n\t}\n\tif err := db.Close(); err != nil {\n\t\tlog.Println(\"close error:\", err)\n\t}\n\treturn true\n}\n\nfunc createDB(t *testing.T, c dktest.ContainerInfo) {\n\tip, port, err := c.Port(defaultPort)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"postgres://yugabyte:yugabyte@%v:%v?sslmode=disable\", ip, port))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err = db.Ping(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\tif _, err = db.Exec(\"CREATE DATABASE migrate\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc getConnectionString(ip, port string, options ...string) string {\n\toptions = append(options, \"sslmode=disable\")\n\n\treturn fmt.Sprintf(\"yugabyte://yugabyte:yugabyte@%v:%v/migrate?%s\", ip, port, strings.Join(options, \"&\"))\n}\n\nfunc Test(t *testing.T) {\n\tt.Run(\"test\", test)\n\tt.Run(\"testMigrate\", testMigrate)\n\tt.Run(\"testMultiStatement\", testMultiStatement)\n\tt.Run(\"testFilterCustomQuery\", testFilterCustomQuery)\n\n\tt.Cleanup(func() {\n\t\tfor _, spec := range specs {\n\t\t\tt.Log(\"Cleaning up \", spec.ImageName)\n\t\t\tif err := spec.Cleanup(); err != nil {\n\t\t\t\tt.Error(\"Error removing \", spec.ImageName, \"error:\", err)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc test(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, ci dktest.ContainerInfo) {\n\t\tcreateDB(t, ci)\n\n\t\tip, port, err := ci.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := getConnectionString(ip, port)\n\t\tc := &YugabyteDB{}\n\t\td, err := c.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\t})\n}\n\nfunc testMigrate(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, ci dktest.ContainerInfo) {\n\t\tcreateDB(t, ci)\n\n\t\tip, port, err := ci.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := getConnectionString(ip, port)\n\t\tc := &YugabyteDB{}\n\t\td, err := c.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tm, err := migrate.NewWithDatabaseInstance(\"file://./examples/migrations\", \"migrate\", d)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.TestMigrate(t, m)\n\t})\n}\n\nfunc testMultiStatement(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, ci dktest.ContainerInfo) {\n\t\tcreateDB(t, ci)\n\n\t\tip, port, err := ci.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := getConnectionString(ip, port)\n\t\tc := &YugabyteDB{}\n\t\td, err := c.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := d.Run(strings.NewReader(\"CREATE TABLE foo (foo text); CREATE TABLE bar (bar text);\")); err != nil {\n\t\t\tt.Fatalf(\"expected err to be nil, got %v\", err)\n\t\t}\n\n\t\t// make sure second table exists\n\t\tvar exists bool\n\t\tif err := d.(*YugabyteDB).db.QueryRow(\"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'bar' AND table_schema = (SELECT current_schema()))\").Scan(&exists); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Fatal(\"expected table bar to exist\")\n\t\t}\n\t})\n}\n\nfunc testFilterCustomQuery(t *testing.T) {\n\tdktesting.ParallelTest(t, specs, func(t *testing.T, ci dktest.ContainerInfo) {\n\t\tcreateDB(t, ci)\n\n\t\tip, port, err := ci.Port(defaultPort)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddr := getConnectionString(ip, port, \"x-custom=foobar\")\n\t\tc := &YugabyteDB{}\n\t\td, err := c.Open(addr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdt.Test(t, d, []byte(\"SELECT 1\"))\n\t})\n}\n"
  },
  {
    "path": "dktesting/dktesting.go",
    "content": "package dktesting\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/dhui/dktest\"\n\t\"github.com/docker/docker/api/types/image\"\n\t\"github.com/docker/docker/client\"\n)\n\n// ContainerSpec holds Docker testing setup specifications\ntype ContainerSpec struct {\n\tImageName string\n\tOptions   dktest.Options\n}\n\n// Cleanup cleanups the ContainerSpec after a test run by removing the ContainerSpec's image\nfunc (s *ContainerSpec) Cleanup() (retErr error) {\n\t// copied from dktest.RunContext()\n\tdc, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion(\"1.41\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := dc.Close(); err != nil && retErr == nil {\n\t\t\tretErr = fmt.Errorf(\"error closing Docker client: %w\", err)\n\t\t}\n\t}()\n\tcleanupTimeout := s.Options.CleanupTimeout\n\tif cleanupTimeout <= 0 {\n\t\tcleanupTimeout = dktest.DefaultCleanupTimeout\n\t}\n\tctx, timeoutCancelFunc := context.WithTimeout(context.Background(), cleanupTimeout)\n\tdefer timeoutCancelFunc()\n\tif _, err := dc.ImageRemove(ctx, s.ImageName, image.RemoveOptions{Force: true, PruneChildren: true}); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// ParallelTest runs Docker tests in parallel\nfunc ParallelTest(t *testing.T, specs []ContainerSpec,\n\ttestFunc func(*testing.T, dktest.ContainerInfo)) {\n\n\tfor i, spec := range specs {\n\t\tspec := spec // capture range variable, see https://goo.gl/60w3p2\n\n\t\t// Only test against one version in short mode\n\t\t// TODO: order is random, maybe always pick first version instead?\n\t\tif i > 0 && testing.Short() {\n\t\t\tt.Logf(\"Skipping %v in short mode\", spec.ImageName)\n\t\t} else {\n\t\t\tt.Run(spec.ImageName, func(t *testing.T) {\n\t\t\t\tt.Parallel()\n\t\t\t\tdktest.Run(t, spec.ImageName, spec.Options, testFunc)\n\t\t\t})\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "dktesting/example_test.go",
    "content": "package dktesting_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\nimport (\n\t\"github.com/dhui/dktest\"\n)\n\nimport (\n\t\"github.com/golang-migrate/migrate/v4/dktesting\"\n)\n\nfunc ExampleParallelTest() {\n\tt := &testing.T{} // Should actually be used in a Test\n\n\tvar isReady = func(ctx context.Context, c dktest.ContainerInfo) bool {\n\t\t// Return true if the container is ready to run tests.\n\t\t// Don't block here though. Use the Context to timeout container ready checks.\n\t\treturn true\n\t}\n\n\tdktesting.ParallelTest(t, []dktesting.ContainerSpec{{ImageName: \"docker_image:9.6\",\n\t\tOptions: dktest.Options{ReadyFunc: isReady}}}, func(t *testing.T, c dktest.ContainerInfo) {\n\t\t// Run your test/s ...\n\t\tt.Fatal(\"...\")\n\t})\n}\n"
  },
  {
    "path": "docker-deploy.sh",
    "content": "#!/bin/bash\n\necho \"$DOCKER_PASSWORD\" | docker login -u \"$DOCKER_USERNAME\" --password-stdin && \\\ndocker build --build-arg VERSION=\"$TRAVIS_TAG\" . -t migrate/migrate -t migrate/migrate:\"$TRAVIS_TAG\" && \\\ndocker push migrate/migrate:\"$TRAVIS_TAG\" && docker push migrate/migrate\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/golang-migrate/migrate/v4\n\ngo 1.24.0\n\nrequire (\n\tcloud.google.com/go/spanner v1.85.0\n\tcloud.google.com/go/storage v1.56.0\n\tgithub.com/Azure/go-autorest/autorest/adal v0.9.16\n\tgithub.com/ClickHouse/clickhouse-go v1.4.3\n\tgithub.com/aws/aws-sdk-go v1.49.6\n\tgithub.com/cenkalti/backoff/v4 v4.1.2\n\tgithub.com/cockroachdb/cockroach-go/v2 v2.1.1\n\tgithub.com/dhui/dktest v0.4.6\n\tgithub.com/docker/docker v28.3.3+incompatible\n\tgithub.com/fsouza/fake-gcs-server v1.17.0\n\tgithub.com/go-sql-driver/mysql v1.5.0\n\tgithub.com/gobuffalo/here v0.6.0\n\tgithub.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556\n\tgithub.com/google/go-github/v39 v39.2.0\n\tgithub.com/jackc/pgconn v1.14.3\n\tgithub.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa\n\tgithub.com/jackc/pgx/v4 v4.18.2\n\tgithub.com/jackc/pgx/v5 v5.7.6\n\tgithub.com/ktrysmt/go-bitbucket v0.6.4\n\tgithub.com/lib/pq v1.10.9\n\tgithub.com/markbates/pkger v0.15.1\n\tgithub.com/mattn/go-sqlite3 v1.14.22\n\tgithub.com/microsoft/go-mssqldb v1.0.0\n\tgithub.com/mutecomm/go-sqlcipher/v4 v4.4.0\n\tgithub.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8\n\tgithub.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba\n\tgithub.com/snowflakedb/gosnowflake v1.6.19\n\tgithub.com/stretchr/testify v1.11.1\n\tgithub.com/xanzy/go-gitlab v0.15.0\n\tgo.mongodb.org/mongo-driver v1.7.5\n\tgolang.org/x/oauth2 v0.30.0\n\tgolang.org/x/tools/godoc v0.1.0-deprecated\n\tgoogle.golang.org/api v0.247.0\n\tmodernc.org/ql v1.0.0\n\tmodernc.org/sqlite v1.18.1\n)\n\nrequire (\n\tcel.dev/expr v0.24.0 // indirect\n\tcloud.google.com/go/auth v0.16.4 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect\n\tcloud.google.com/go/monitoring v1.24.2 // indirect\n\tgithub.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect\n\tgithub.com/containerd/errdefs v1.0.0 // indirect\n\tgithub.com/containerd/errdefs/pkg v0.3.0 // indirect\n\tgithub.com/distribution/reference v0.6.0 // indirect\n\tgithub.com/docker/go-connections v0.5.0 // indirect\n\tgithub.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/go-jose/go-jose/v4 v4.0.5 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/jackc/puddle/v2 v2.2.2 // indirect\n\tgithub.com/moby/docker-image-spec v1.3.1 // indirect\n\tgithub.com/moby/sys/sequential v0.6.0 // indirect\n\tgithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect\n\tgithub.com/spiffe/go-spiffe/v2 v2.5.0 // indirect\n\tgithub.com/zeebo/errs v1.4.0 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect\n\tgo.opentelemetry.io/otel v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.40.0 // indirect\n\tgo.opentelemetry.io/proto/otlp v1.3.1 // indirect\n\tgolang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 // indirect\n\tgolang.org/x/tools v0.38.0 // indirect\n)\n\nrequire (\n\tcloud.google.com/go v0.121.6 // indirect\n\tcloud.google.com/go/compute/metadata v0.8.0 // indirect\n\tcloud.google.com/go/iam v1.5.2 // indirect\n\tcloud.google.com/go/longrunning v0.6.7 // indirect\n\tgithub.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect\n\tgithub.com/99designs/keyring v1.2.1 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 // indirect\n\tgithub.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect\n\tgithub.com/Azure/go-autorest v14.2.0+incompatible // indirect\n\tgithub.com/Azure/go-autorest/autorest/date v0.3.0 // indirect\n\tgithub.com/Azure/go-autorest/logger v0.2.1 // indirect\n\tgithub.com/Azure/go-autorest/tracing v0.6.0 // indirect\n\tgithub.com/Microsoft/go-winio v0.6.2 // indirect\n\tgithub.com/andybalholm/brotli v1.0.4 // indirect\n\tgithub.com/apache/arrow/go/v10 v10.0.1 // indirect\n\tgithub.com/apache/thrift v0.16.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2 v1.16.16 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/credentials v1.12.20 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.33 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/s3 v1.27.11 // indirect\n\tgithub.com/aws/smithy-go v1.13.3 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 // indirect\n\tgithub.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect\n\tgithub.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369 // indirect\n\tgithub.com/danieljoos/wincred v1.1.2 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/docker/go-units v0.5.0 // indirect\n\tgithub.com/dvsekhvalnov/jose2go v1.7.0 // indirect\n\tgithub.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect\n\tgithub.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect\n\tgithub.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect\n\tgithub.com/gabriel-vasile/mimetype v1.4.1 // indirect\n\tgithub.com/go-stack/stack v1.8.0 // indirect\n\tgithub.com/goccy/go-json v0.9.11 // indirect\n\tgithub.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang-jwt/jwt/v4 v4.5.2 // indirect\n\tgithub.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect\n\tgithub.com/golang-sql/sqlexp v0.1.0 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect\n\tgithub.com/golang/snappy v0.0.4 // indirect\n\tgithub.com/google/flatbuffers v2.0.8+incompatible // indirect\n\tgithub.com/google/go-querystring v1.1.0 // indirect\n\tgithub.com/google/s2a-go v0.1.9 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.15.0 // indirect\n\tgithub.com/gorilla/handlers v1.4.2 // indirect\n\tgithub.com/gorilla/mux v1.7.4 // indirect\n\tgithub.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect\n\tgithub.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect\n\tgithub.com/jackc/chunkreader/v2 v2.0.1 // 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-20240606120523-5a60cdf6a761 // indirect\n\tgithub.com/jackc/pgtype v1.14.0 // indirect\n\tgithub.com/jmespath/go-jmespath v0.4.0 // indirect\n\tgithub.com/k0kubun/pp v2.3.0+incompatible // indirect\n\tgithub.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect\n\tgithub.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect\n\tgithub.com/klauspost/asmfmt v1.3.2 // indirect\n\tgithub.com/klauspost/compress v1.15.11 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.0.9 // indirect\n\tgithub.com/mattn/go-colorable v0.1.6 // indirect\n\tgithub.com/mattn/go-isatty v0.0.16 // indirect\n\tgithub.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect\n\tgithub.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect\n\tgithub.com/mitchellh/mapstructure v1.1.2 // indirect\n\tgithub.com/moby/term v0.5.0 // indirect\n\tgithub.com/morikuni/aec v1.0.0 // indirect\n\tgithub.com/mtibben/percent v0.2.1 // indirect\n\tgithub.com/onsi/ginkgo v1.16.4 // indirect\n\tgithub.com/onsi/gomega v1.15.0 // indirect\n\tgithub.com/opencontainers/go-digest v1.0.0 // indirect\n\tgithub.com/opencontainers/image-spec v1.1.0 // indirect\n\tgithub.com/pierrec/lz4/v4 v4.1.16 // indirect\n\tgithub.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect\n\tgithub.com/rqlite/gorqlite v0.0.0-20230708021416-2acd02b70b79\n\tgithub.com/shopspring/decimal v1.2.0 // indirect\n\tgithub.com/sirupsen/logrus v1.9.3 // indirect\n\tgithub.com/xdg-go/pbkdf2 v1.0.0 // indirect\n\tgithub.com/xdg-go/scram v1.1.1 // indirect\n\tgithub.com/xdg-go/stringprep v1.0.3 // indirect\n\tgithub.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect\n\tgithub.com/zeebo/xxh3 v1.0.2 // indirect\n\tgitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b // indirect\n\tgo.opencensus.io v0.24.0 // indirect\n\tgolang.org/x/crypto v0.45.0 // indirect\n\tgolang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect\n\tgolang.org/x/mod v0.29.0 // indirect\n\tgolang.org/x/net v0.47.0 // indirect\n\tgolang.org/x/sync v0.18.0 // indirect\n\tgolang.org/x/sys v0.40.0 // indirect\n\tgolang.org/x/term v0.37.0 // indirect\n\tgolang.org/x/text v0.31.0 // indirect\n\tgolang.org/x/time v0.12.0 // indirect\n\tgolang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect\n\tgoogle.golang.org/grpc v1.74.2 // indirect\n\tgoogle.golang.org/protobuf v1.36.7 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tlukechampine.com/uint128 v1.2.0 // indirect\n\tmodernc.org/b v1.0.0 // indirect\n\tmodernc.org/cc/v3 v3.36.3 // indirect\n\tmodernc.org/ccgo/v3 v3.16.9 // indirect\n\tmodernc.org/db v1.0.0 // indirect\n\tmodernc.org/file v1.0.0 // indirect\n\tmodernc.org/fileutil v1.0.0 // indirect\n\tmodernc.org/golex v1.0.0 // indirect\n\tmodernc.org/internal v1.0.0 // indirect\n\tmodernc.org/libc v1.17.1 // indirect\n\tmodernc.org/lldb v1.0.0 // indirect\n\tmodernc.org/mathutil v1.5.0 // indirect\n\tmodernc.org/memory v1.2.1 // indirect\n\tmodernc.org/opt v0.1.3 // indirect\n\tmodernc.org/sortutil v1.1.0 // indirect\n\tmodernc.org/strutil v1.1.3 // indirect\n\tmodernc.org/token v1.0.0 // indirect\n\tmodernc.org/zappy v1.0.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=\ncel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=\ncloud.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.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U=\ncloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=\ncloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=\ncloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU=\ncloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=\ncloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=\ncloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=\ncloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=\ncloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=\ncloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=\ncloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=\ncloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=\ncloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=\ncloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o=\ncloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE=\ncloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM=\ncloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ=\ncloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw=\ncloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY=\ncloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg=\ncloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ=\ncloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k=\ncloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw=\ncloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI=\ncloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4=\ncloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M=\ncloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE=\ncloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE=\ncloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk=\ncloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc=\ncloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8=\ncloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc=\ncloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04=\ncloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8=\ncloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY=\ncloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM=\ncloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc=\ncloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU=\ncloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI=\ncloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8=\ncloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno=\ncloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak=\ncloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84=\ncloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A=\ncloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E=\ncloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4=\ncloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0=\ncloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY=\ncloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k=\ncloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ=\ncloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk=\ncloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0=\ncloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc=\ncloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI=\ncloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ=\ncloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI=\ncloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08=\ncloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o=\ncloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s=\ncloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0=\ncloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ=\ncloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY=\ncloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo=\ncloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg=\ncloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw=\ncloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY=\ncloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw=\ncloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI=\ncloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo=\ncloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0=\ncloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E=\ncloud.google.com/go/auth v0.16.4 h1:fXOAIQmkApVvcIn7Pc2+5J8QTMVbUGLscnSVNl11su8=\ncloud.google.com/go/auth v0.16.4/go.mod h1:j10ncYwjX/g3cdX7GpEzsdM+d+ZNsXAbb6qXA7p1Y5M=\ncloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=\ncloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=\ncloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=\ncloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8=\ncloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8=\ncloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM=\ncloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU=\ncloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc=\ncloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI=\ncloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss=\ncloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE=\ncloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE=\ncloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g=\ncloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4=\ncloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8=\ncloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM=\ncloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU=\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/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA=\ncloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw=\ncloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc=\ncloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E=\ncloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac=\ncloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q=\ncloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU=\ncloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY=\ncloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s=\ncloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI=\ncloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y=\ncloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss=\ncloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc=\ncloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM=\ncloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI=\ncloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0=\ncloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk=\ncloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q=\ncloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg=\ncloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590=\ncloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8=\ncloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk=\ncloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk=\ncloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE=\ncloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU=\ncloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U=\ncloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA=\ncloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M=\ncloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg=\ncloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s=\ncloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM=\ncloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk=\ncloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA=\ncloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY=\ncloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI=\ncloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4=\ncloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI=\ncloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y=\ncloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs=\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/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=\ncloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=\ncloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=\ncloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=\ncloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE=\ncloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo=\ncloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA=\ncloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=\ncloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=\ncloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=\ncloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=\ncloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=\ncloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=\ncloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=\ncloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=\ncloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=\ncloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY=\ncloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck=\ncloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w=\ncloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg=\ncloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo=\ncloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4=\ncloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM=\ncloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA=\ncloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I=\ncloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4=\ncloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI=\ncloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s=\ncloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0=\ncloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs=\ncloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc=\ncloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE=\ncloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM=\ncloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M=\ncloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0=\ncloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8=\ncloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM=\ncloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ=\ncloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE=\ncloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo=\ncloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE=\ncloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0=\ncloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA=\ncloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE=\ncloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38=\ncloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w=\ncloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8=\ncloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I=\ncloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ=\ncloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM=\ncloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA=\ncloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A=\ncloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ=\ncloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs=\ncloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s=\ncloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI=\ncloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4=\ncloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo=\ncloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA=\ncloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c=\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/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM=\ncloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c=\ncloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo=\ncloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ=\ncloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g=\ncloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4=\ncloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs=\ncloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww=\ncloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c=\ncloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s=\ncloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI=\ncloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ=\ncloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4=\ncloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0=\ncloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8=\ncloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek=\ncloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0=\ncloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM=\ncloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4=\ncloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE=\ncloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM=\ncloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q=\ncloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4=\ncloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU=\ncloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU=\ncloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k=\ncloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4=\ncloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM=\ncloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs=\ncloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y=\ncloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg=\ncloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE=\ncloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk=\ncloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w=\ncloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc=\ncloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY=\ncloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU=\ncloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI=\ncloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8=\ncloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M=\ncloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc=\ncloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw=\ncloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw=\ncloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY=\ncloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w=\ncloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI=\ncloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs=\ncloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg=\ncloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE=\ncloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk=\ncloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg=\ncloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY=\ncloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08=\ncloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw=\ncloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA=\ncloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c=\ncloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM=\ncloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA=\ncloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w=\ncloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM=\ncloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0=\ncloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60=\ncloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo=\ncloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg=\ncloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o=\ncloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A=\ncloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw=\ncloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0=\ncloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0=\ncloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E=\ncloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw=\ncloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA=\ncloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI=\ncloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y=\ncloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc=\ncloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM=\ncloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o=\ncloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo=\ncloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=\ncloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=\ncloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc=\ncloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc=\ncloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg=\ncloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE=\ncloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY=\ncloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY=\ncloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0=\ncloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=\ncloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=\ncloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc=\ncloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A=\ncloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk=\ncloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo=\ncloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74=\ncloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM=\ncloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY=\ncloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4=\ncloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs=\ncloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g=\ncloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o=\ncloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE=\ncloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA=\ncloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg=\ncloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0=\ncloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg=\ncloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w=\ncloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24=\ncloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI=\ncloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=\ncloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=\ncloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE=\ncloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8=\ncloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY=\ncloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8=\ncloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08=\ncloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo=\ncloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw=\ncloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M=\ncloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=\ncloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=\ncloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE=\ncloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=\ncloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=\ncloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=\ncloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=\ncloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE=\ncloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM=\ncloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA=\ncloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI=\ncloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw=\ncloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY=\ncloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4=\ncloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w=\ncloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I=\ncloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE=\ncloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM=\ncloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA=\ncloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY=\ncloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM=\ncloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY=\ncloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s=\ncloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8=\ncloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI=\ncloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo=\ncloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk=\ncloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4=\ncloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w=\ncloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw=\ncloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=\ncloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=\ncloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA=\ncloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o=\ncloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM=\ncloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8=\ncloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E=\ncloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM=\ncloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8=\ncloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4=\ncloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY=\ncloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ=\ncloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU=\ncloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k=\ncloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU=\ncloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY=\ncloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34=\ncloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA=\ncloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0=\ncloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE=\ncloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ=\ncloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4=\ncloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs=\ncloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI=\ncloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA=\ncloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk=\ncloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ=\ncloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE=\ncloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc=\ncloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc=\ncloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs=\ncloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg=\ncloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo=\ncloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw=\ncloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw=\ncloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E=\ncloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU=\ncloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70=\ncloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo=\ncloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs=\ncloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0=\ncloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA=\ncloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk=\ncloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg=\ncloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE=\ncloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw=\ncloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc=\ncloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0=\ncloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI=\ncloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg=\ncloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs=\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/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI=\ncloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0=\ncloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8=\ncloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4=\ncloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg=\ncloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k=\ncloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM=\ncloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4=\ncloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o=\ncloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk=\ncloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo=\ncloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE=\ncloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U=\ncloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA=\ncloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c=\ncloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg=\ncloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4=\ncloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac=\ncloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg=\ncloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c=\ncloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs=\ncloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70=\ncloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ=\ncloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y=\ncloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A=\ncloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA=\ncloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM=\ncloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ=\ncloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA=\ncloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0=\ncloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots=\ncloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo=\ncloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI=\ncloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU=\ncloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg=\ncloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA=\ncloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4=\ncloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY=\ncloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc=\ncloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y=\ncloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14=\ncloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do=\ncloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo=\ncloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM=\ncloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg=\ncloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s=\ncloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI=\ncloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk=\ncloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44=\ncloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc=\ncloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc=\ncloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA=\ncloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4=\ncloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4=\ncloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU=\ncloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4=\ncloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0=\ncloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU=\ncloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q=\ncloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA=\ncloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8=\ncloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0=\ncloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU=\ncloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc=\ncloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk=\ncloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk=\ncloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0=\ncloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag=\ncloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU=\ncloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s=\ncloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA=\ncloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc=\ncloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk=\ncloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs=\ncloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg=\ncloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4=\ncloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U=\ncloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY=\ncloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s=\ncloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco=\ncloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo=\ncloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc=\ncloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4=\ncloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E=\ncloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU=\ncloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec=\ncloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA=\ncloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4=\ncloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw=\ncloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A=\ncloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos=\ncloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk=\ncloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M=\ncloud.google.com/go/spanner v1.85.0 h1:VVO3yW+0+Yx9tg4SQaZvJHGAnU6qCnGXQ3NX4E3+src=\ncloud.google.com/go/spanner v1.85.0/go.mod h1:9zhmtOEoYV06nE4Orbin0dc/ugHzZW9yXuvaM61rpxs=\ncloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=\ncloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=\ncloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0=\ncloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco=\ncloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0=\ncloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI=\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=\ncloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=\ncloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc=\ncloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=\ncloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=\ncloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4=\ncloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI=\ncloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU=\ncloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w=\ncloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I=\ncloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4=\ncloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw=\ncloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw=\ncloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g=\ncloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM=\ncloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA=\ncloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c=\ncloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8=\ncloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4=\ncloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc=\ncloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ=\ncloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg=\ncloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM=\ncloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28=\ncloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y=\ncloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA=\ncloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk=\ncloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=\ncloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=\ncloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs=\ncloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg=\ncloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0=\ncloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos=\ncloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos=\ncloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk=\ncloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw=\ncloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg=\ncloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk=\ncloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ=\ncloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ=\ncloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU=\ncloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4=\ncloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M=\ncloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU=\ncloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU=\ncloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0=\ncloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo=\ncloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo=\ncloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY=\ncloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E=\ncloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY=\ncloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0=\ncloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE=\ncloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g=\ncloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc=\ncloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY=\ncloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208=\ncloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8=\ncloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY=\ncloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w=\ncloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8=\ncloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes=\ncloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE=\ncloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg=\ncloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc=\ncloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A=\ncloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg=\ncloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo=\ncloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ=\ncloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng=\ncloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=\ncloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=\ncloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M=\ncloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA=\ncloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=\ngit.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=\ngithub.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=\ngithub.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=\ngithub.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o=\ngithub.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0 h1:rTnT/Jrcm+figWlYz4Ixzt0SJVR2cMC8lvZcimipiEY=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1 h1:T8quHYlUGyb/oqtSTwqlCr1ilJHrDv+ZtpSfo+hm1BU=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1/go.mod h1:gLa1CL2RNE4s7M3yopJ/p0iq5DdY6Yv5ZUt9MTRZOQM=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 h1:+5VZ72z0Qan5Bog5C+ZkgSqUbeVUd9wgtHOrIKuc5b8=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=\ngithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 h1:u/LLAOFgsMv7HmNL4Qufg58y+qElGOt5qv0z1mURkRY=\ngithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0/go.mod h1:2e8rMJtl2+2j+HXbTBwnyGpm5Nou7KhvSfxOq8JpTag=\ngithub.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=\ngithub.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=\ngithub.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=\ngithub.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.16 h1:P8An8Z9rH1ldbOLdFpxYorgOt2sywL9V24dAwWHPuGc=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.16/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A=\ngithub.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=\ngithub.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=\ngithub.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=\ngithub.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=\ngithub.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=\ngithub.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v0.8.1 h1:oPdPEZFSbl7oSPEAIPMPBMUmiL+mqgzBJwM/9qYcwNg=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v0.8.1/go.mod h1:4qFor3D/HDsvBME35Xy9rwW9DecL+M2sNw1ybjPtwA0=\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/ClickHouse/clickhouse-go v1.4.3 h1:iAFMa2UrQdR5bHJ2/yaSLffZkxpcOYQMCUuKeNXGdqc=\ngithub.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=\ngithub.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3 h1:2afWGsMzkIcN8Qm4mgPJKZWyroE5QBszMiDMYEBrnfw=\ngithub.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=\ngithub.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c h1:RGWPOewvKIROun94nF7v2cua9qP+thov/7M50KEoeSU=\ngithub.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=\ngithub.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=\ngithub.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=\ngithub.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=\ngithub.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=\ngithub.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=\ngithub.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=\ngithub.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=\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/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\ngithub.com/apache/arrow/go/v10 v10.0.1 h1:n9dERvixoC/1JjDmBcs9FPaEryoANa2sCgVFo6ez9cI=\ngithub.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=\ngithub.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI=\ngithub.com/apache/thrift v0.16.0 h1:qEy6UW60iVOlUy+b9ZR0d5WzUWYGOo4HfopoyBaNmoY=\ngithub.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=\ngithub.com/aws/aws-sdk-go v1.49.6 h1:yNldzF5kzLBRvKlKz1S0bkvc2+04R1kt13KfBWQBfFA=\ngithub.com/aws/aws-sdk-go v1.49.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=\ngithub.com/aws/aws-sdk-go-v2 v1.16.16 h1:M1fj4FE2lB4NzRb9Y0xdWsn2P0+2UHVxwKyOa4YJNjk=\ngithub.com/aws/aws-sdk-go-v2 v1.16.16/go.mod h1:SwiyXi/1zTUZ6KIAmLK5V5ll8SiURNUYOqTerZPaF9k=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 h1:tcFliCWne+zOuUfKNRn8JdFBuWPDuISDH08wD2ULkhk=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8/go.mod h1:JTnlBSot91steJeti4ryyu/tLd4Sk84O5W22L7O2EQU=\ngithub.com/aws/aws-sdk-go-v2/config v1.17.7 h1:odVM52tFHhpqZBKNjVW5h+Zt1tKHbhdTQRb+0WHrNtw=\ngithub.com/aws/aws-sdk-go-v2/config v1.17.7/go.mod h1:dN2gja/QXxFF15hQreyrqYhLBaQo1d9ZKe/v/uplQoI=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.12.20 h1:9+ZhlDY7N9dPnUmf7CDfW9In4sW5Ff3bh7oy4DzS1IE=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.12.20/go.mod h1:UKY5HyIux08bbNA7Blv4PcXQ8cTkGh7ghHMFklaviR4=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17 h1:r08j4sbZu/RVi+BNxkBJwPMUYY3P8mgSDuKkZ/ZN1lE=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17/go.mod h1:yIkQcCDYNsZfXpd5UX2Cy+sWA1jPgIhGTw9cOBzfVnQ=\ngithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.33 h1:fAoVmNGhir6BR+RU0/EI+6+D7abM+MCwWf8v4ip5jNI=\ngithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.33/go.mod h1:84XgODVR8uRhmOnUkKGUZKqIMxmjmLOR8Uyp7G/TPwc=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23 h1:s4g/wnzMf+qepSNgTvaQQHNxyMLKSawNhKCPNy++2xY=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23/go.mod h1:2DFxAQ9pfIRy0imBCJv+vZ2X6RKxves6fbnEuSry6b4=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17 h1:/K482T5A3623WJgWT8w1yRAFK4RzGzEl7y39yhtn9eA=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17/go.mod h1:pRwaTYCJemADaqCbUAxltMoHKata7hmB5PjEXeu0kfg=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.3.24 h1:wj5Rwc05hvUSvKuOF29IYb9QrCLjU+rHAy/x/o0DK2c=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.3.24/go.mod h1:jULHjqqjDlbyTa7pfM7WICATnOv+iOhjletM3N0Xbu8=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14 h1:ZSIPAkAsCCjYrhqfw2+lNzWDzxzHXEckFkTePL5RSWQ=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14/go.mod h1:AyGgqiKv9ECM6IZeNQtdT8NnMvUb3/2wokeq2Fgryto=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 h1:Lh1AShsuIJTwMkoxVCAYPJgNG5H+eN6SmoUn8nOZ5wE=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9/go.mod h1:a9j48l6yL5XINLHLcOKInjdvknN+vWqPBxqeIDw7ktw=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18 h1:BBYoNQt2kUZUUK4bIPsKrCcjVPUMNsgQpNAwhznK/zo=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18/go.mod h1:NS55eQ4YixUJPTC+INxi2/jCqe1y2Uw3rnh9wEOVJxY=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17 h1:Jrd/oMh0PKQc6+BowB+pLEwLIgaQF29eYbe7E1Av9Ug=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17/go.mod h1:4nYOrY41Lrbk2170/BGkcJKBhws9Pfn8MG3aGqjjeFI=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17 h1:HfVVR1vItaG6le+Bpw6P4midjBDMKnjMyZnw9MXYUcE=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17/go.mod h1:YqMdV+gEKCQ59NrB7rzrJdALeBIsYiVi8Inj3+KcqHI=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.27.11 h1:3/gm/JTX9bX8CpzTgIlrtYpB3EVBDxyg/GY/QdcIEZw=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.27.11/go.mod h1:fmgDANqTUCxciViKl9hb/zD5LFbvPINFRgWhDbR+vZo=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.11.23 h1:pwvCchFUEnlceKIgPUouBJwK81aCkQ8UDMORfeFtW10=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.11.23/go.mod h1:/w0eg9IhFGjGyyncHIQrXtU8wvNsTJOP0R6PPj0wf80=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.5 h1:GUnZ62TevLqIoDyHeiWj2P7EqaosgakBKVvWriIdLQY=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.5/go.mod h1:csZuQY65DAdFBt1oIjO5hhBR49kQqop4+lcuCjf2arA=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.16.19 h1:9pPi0PsFNAGILFfPCk8Y0iyEBGc6lu6OQ97U7hmdesg=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.16.19/go.mod h1:h4J3oPZQbxLhzGnk+j9dfYHi5qIOVJ5kczZd658/ydM=\ngithub.com/aws/smithy-go v1.13.3 h1:l7LYxGuzK6/K+NzJ2mC+VvLUbae0sL3bXU//04MkmnA=\ngithub.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=\ngithub.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=\ngithub.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=\ngithub.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=\ngithub.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=\ngithub.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=\ngithub.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=\ngithub.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=\ngithub.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=\ngithub.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=\ngithub.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=\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.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.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/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=\ngithub.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=\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/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/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/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=\ngithub.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=\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/cockroachdb/cockroach-go/v2 v2.1.1 h1:3XzfSMuUT0wBe1a3o5C0eOTcArhmmFAg2Jzh/7hhKqo=\ngithub.com/cockroachdb/cockroach-go/v2 v2.1.1/go.mod h1:7NtUnP6eK+l6k483WSYNrq3Kb23bWV10IRV1TyeSpwM=\ngithub.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=\ngithub.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=\ngithub.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=\ngithub.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=\ngithub.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=\ngithub.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=\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/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.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=\ngithub.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=\ngithub.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369 h1:XNT/Zf5l++1Pyg08/HV04ppB0gKxAqtZQBRYiYrUuYk=\ngithub.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=\ngithub.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=\ngithub.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4=\ngithub.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU=\ngithub.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=\ngithub.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=\ngithub.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=\ngithub.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=\ngithub.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=\ngithub.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=\ngithub.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=\ngithub.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=\ngithub.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/dvsekhvalnov/jose2go v1.7.0 h1:bnQc8+GMnidJZA8zc6lLEAb4xNrIqHwO+9TzqvtQZPo=\ngithub.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=\ngithub.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8=\ngithub.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=\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/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34=\ngithub.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q=\ngithub.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=\ngithub.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=\ngithub.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=\ngithub.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=\ngithub.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=\ngithub.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=\ngithub.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=\ngithub.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=\ngithub.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=\ngithub.com/form3tech-oss/jwt-go v3.2.5+incompatible h1:/l4kBbb4/vGSsdtB5nUe8L7B9mImVMaBPw9L/0TBHU8=\ngithub.com/form3tech-oss/jwt-go v3.2.5+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/fsouza/fake-gcs-server v1.17.0 h1:OeH75kBZcZa3ZE+zz/mFdJ2btt9FgqfjI7gIh9+5fvk=\ngithub.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw=\ngithub.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q=\ngithub.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=\ngithub.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=\ngithub.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=\ngithub.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=\ngithub.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=\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-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=\ngithub.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=\ngithub.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=\ngithub.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=\ngithub.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=\ngithub.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=\ngithub.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=\ngithub.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=\ngithub.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=\ngithub.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=\ngithub.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=\ngithub.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI=\ngithub.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=\ngithub.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=\ngithub.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=\ngithub.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556 h1:N/MD/sr6o61X+iZBAT2qEUF023s4KbA8RWfKzl0L6MQ=\ngithub.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY=\ngithub.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=\ngithub.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=\ngithub.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=\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.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=\ngithub.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\ngithub.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=\ngithub.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\ngithub.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=\ngithub.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=\ngithub.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=\ngithub.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=\ngithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=\ngithub.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=\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 h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\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/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=\ngithub.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=\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/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/protobuf v1.5.3/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/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=\ngithub.com/golang/snappy v0.0.4/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/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM=\ngithub.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=\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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/go-github/v39 v39.2.0 h1:rNNM311XtPOz5rDdsJXAp2o8F67X9FnROXTvto3aSnQ=\ngithub.com/google/go-github/v39 v39.2.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE=\ngithub.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=\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/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=\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/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=\ngithub.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=\ngithub.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=\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/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=\ngithub.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=\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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=\ngithub.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=\ngithub.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=\ngithub.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=\ngithub.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=\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/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo=\ngithub.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=\ngithub.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=\ngithub.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=\ngithub.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=\ngithub.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=\ngithub.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=\ngithub.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=\ngithub.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=\ngithub.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=\ngithub.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=\ngithub.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=\ngithub.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=\ngithub.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=\ngithub.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=\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.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=\ngithub.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=\ngithub.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=\ngithub.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=\ngithub.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=\ngithub.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=\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/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.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=\ngithub.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=\ngithub.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=\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/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw=\ngithub.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=\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.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\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-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=\ngithub.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=\ngithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=\ngithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/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.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=\ngithub.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=\ngithub.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=\ngithub.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=\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.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=\ngithub.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=\ngithub.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=\ngithub.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA=\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/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=\ngithub.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=\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.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=\ngithub.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=\ngithub.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=\ngithub.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=\ngithub.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=\ngithub.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=\ngithub.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=\ngithub.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=\ngithub.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=\ngithub.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=\ngithub.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=\ngithub.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=\ngithub.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=\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/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=\ngithub.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=\ngithub.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=\ngithub.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=\ngithub.com/k0kubun/pp v2.3.0+incompatible h1:EKhKbi34VQDWJtq+zpsKSEhkHHs9w2P8Izbq8IhLVSo=\ngithub.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=\ngithub.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=\ngithub.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=\ngithub.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=\ngithub.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=\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/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4=\ngithub.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=\ngithub.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=\ngithub.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=\ngithub.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=\ngithub.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=\ngithub.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=\ngithub.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=\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/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=\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/ktrysmt/go-bitbucket v0.6.4 h1:C8dUGp0qkwncKtAnozHCbbqhptefzEd1I0sfnuy9rYQ=\ngithub.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\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.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\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/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=\ngithub.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=\ngithub.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=\ngithub.com/markbates/pkger v0.15.1 h1:3MPelV53RnGSW07izx5xGxl4e/sdRD6zqseIk0rMASY=\ngithub.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=\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.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=\ngithub.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=\ngithub.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\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.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=\ngithub.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=\ngithub.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=\ngithub.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=\ngithub.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=\ngithub.com/microsoft/go-mssqldb v1.0.0 h1:k2p2uuG8T5T/7Hp7/e3vMGTnnR0sU4h8d1CcC71iLHU=\ngithub.com/microsoft/go-mssqldb v1.0.0/go.mod h1:+4wZTUnz/SV6nffv+RRRB/ss8jPng5Sho2SmM1l2ts4=\ngithub.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs=\ngithub.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=\ngithub.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=\ngithub.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=\ngithub.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=\ngithub.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=\ngithub.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=\ngithub.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=\ngithub.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=\ngithub.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=\ngithub.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=\ngithub.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=\ngithub.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=\ngithub.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=\ngithub.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=\ngithub.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=\ngithub.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=\ngithub.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=\ngithub.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=\ngithub.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=\ngithub.com/mutecomm/go-sqlcipher/v4 v4.4.0 h1:sV1tWCWGAVlPhNGT95Q+z/txFxuhAYWwHD1afF5bMZg=\ngithub.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY=\ngithub.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8 h1:P48LjvUQpTReR3TQRbxSeSBsMXzfK0uol7eRcr7VBYQ=\ngithub.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA=\ngithub.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba h1:fhFP5RliM2HW/8XdcO5QngSfFli9GcRIpMXvypTQt6E=\ngithub.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=\ngithub.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=\ngithub.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=\ngithub.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=\ngithub.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=\ngithub.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=\ngithub.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=\ngithub.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=\ngithub.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=\ngithub.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=\ngithub.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=\ngithub.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=\ngithub.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=\ngithub.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=\ngithub.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=\ngithub.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=\ngithub.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=\ngithub.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=\ngithub.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=\ngithub.com/pierrec/lz4/v4 v4.1.16 h1:kQPfno+wyx6C5572ABwV+Uo3pDFzQ7yhyGchSyRda0c=\ngithub.com/pierrec/lz4/v4 v4.1.16/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=\ngithub.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=\ngithub.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=\ngithub.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=\ngithub.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=\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/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=\ngithub.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=\ngithub.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=\ngithub.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=\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.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/rqlite/gorqlite v0.0.0-20230708021416-2acd02b70b79 h1:V7x0hCAgL8lNGezuex1RW1sh7VXXCqfw8nXZti66iFg=\ngithub.com/rqlite/gorqlite v0.0.0-20230708021416-2acd02b70b79/go.mod h1:xF/KoXmrRyahPfo5L7Szb5cAAUl53dMWBh9cMruGEZg=\ngithub.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=\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/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=\ngithub.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=\ngithub.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=\ngithub.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=\ngithub.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=\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/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.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/snowflakedb/gosnowflake v1.6.19 h1:KSHXrQ5o7uso25hNIzi/RObXtnSGkFgie91X82KcvMY=\ngithub.com/snowflakedb/gosnowflake v1.6.19/go.mod h1:FM1+PWUdwB9udFDsXdfD58NONC0m+MlOSmQRvimobSM=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=\ngithub.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=\ngithub.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=\ngithub.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=\ngithub.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=\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/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\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/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=\ngithub.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=\ngithub.com/xanzy/go-gitlab v0.15.0 h1:rWtwKTgEnXyNUGrOArN7yyc3THRkpYcKXIXia9abywQ=\ngithub.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=\ngithub.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=\ngithub.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=\ngithub.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=\ngithub.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=\ngithub.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=\ngithub.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=\ngithub.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=\ngithub.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=\ngithub.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=\ngithub.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=\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/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=\ngithub.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=\ngithub.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=\ngithub.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=\ngithub.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=\ngithub.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=\ngithub.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=\ngitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs=\ngitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=\ngo.mongodb.org/mongo-driver v1.7.5 h1:ny3p0reEpgsR2cfA5cjgwFZg3Cv/ofFh/8jbhGtz9VI=\ngo.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=\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.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=\ngo.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=\ngo.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=\ngo.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=\ngo.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=\ngo.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=\ngo.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=\ngo.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=\ngo.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=\ngo.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=\ngo.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=\ngo.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=\ngo.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=\ngo.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=\ngo.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=\ngo.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=\ngo.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=\ngo.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=\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-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-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/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-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\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-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/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-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=\ngolang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=\ngolang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/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-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=\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/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=\ngolang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=\ngolang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=\ngolang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=\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/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=\ngolang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=\ngolang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=\ngolang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=\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/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=\ngolang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=\ngolang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=\ngolang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181108082009-03003ca0c849/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-20190225153610-fe579d43d832/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-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-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-20200520004742-59133d7f0dd7/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-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\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-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=\ngolang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/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-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=\ngolang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=\ngolang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=\ngolang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=\ngolang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=\ngolang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=\ngolang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=\ngolang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=\ngolang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=\ngolang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/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-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-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/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=\ngolang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=\ngolang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=\ngolang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=\ngolang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=\ngolang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=\ngolang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=\ngolang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=\ngolang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=\ngolang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=\ngolang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=\ngolang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=\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-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-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=\ngolang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/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-20180909124046-d0be0721c37e/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-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190904154756-749cb33beabd/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-20191120155948-bd437916bb0e/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-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-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-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-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-20210112080510-489259a85091/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-20210304124612-50617c2ba197/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-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-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-20210616045830-e2b7044e8c71/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-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/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-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211019181941-9d821ace8654/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-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-20220224120231-95c6836cb0e7/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-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-20220503163025-988cb79eb6c6/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-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/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-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=\ngolang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=\ngolang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=\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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=\ngolang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=\ngolang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=\ngolang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=\ngolang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=\ngolang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=\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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=\ngolang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=\ngolang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=\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/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=\ngolang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=\ngolang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/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-20190206041539-40960b6deb8e/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-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-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\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-20190927191325-030b2cf1153e/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-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-20201124115921-2c860bdd6e78/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-20201224043029-2b0845dc783e/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/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=\ngolang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=\ngolang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=\ngolang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk=\ngolang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg=\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=\ngolang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngolang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngolang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=\ngolang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=\ngonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=\ngonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=\ngonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0=\ngonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E=\ngonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA=\ngonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=\ngonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=\ngonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY=\ngonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo=\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.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.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.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=\ngoogle.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=\ngoogle.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=\ngoogle.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=\ngoogle.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g=\ngoogle.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=\ngoogle.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=\ngoogle.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI=\ngoogle.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=\ngoogle.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=\ngoogle.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=\ngoogle.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08=\ngoogle.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70=\ngoogle.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo=\ngoogle.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=\ngoogle.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=\ngoogle.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=\ngoogle.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=\ngoogle.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=\ngoogle.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0=\ngoogle.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=\ngoogle.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc=\ngoogle.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM=\ngoogle.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\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-20180817151627-c66870c02cf8/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-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-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=\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-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-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\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-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE=\ngoogle.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc=\ngoogle.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw=\ngoogle.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=\ngoogle.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=\ngoogle.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=\ngoogle.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=\ngoogle.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=\ngoogle.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=\ngoogle.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=\ngoogle.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo=\ngoogle.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE=\ngoogle.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA=\ngoogle.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=\ngoogle.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=\ngoogle.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA=\ngoogle.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=\ngoogle.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=\ngoogle.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=\ngoogle.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=\ngoogle.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=\ngoogle.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=\ngoogle.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=\ngoogle.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=\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.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.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=\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.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=\ngoogle.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=\ngoogle.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=\ngoogle.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=\ngoogle.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=\ngoogle.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=\ngoogle.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=\ngoogle.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=\ngoogle.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=\ngoogle.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=\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/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=\ngoogle.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=\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-20200902074654-038fdea0a05b/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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=\ngopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=\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.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.7/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg=\ngorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=\ngorm.io/gorm v1.21.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=\ngotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk=\ngotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ=\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=\nhonnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=\nlukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=\nlukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=\nlukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=\nmodernc.org/b v1.0.0 h1:vpvqeyp17ddcQWF29Czawql4lDdABCDRbXRAS4+aF2o=\nmodernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg=\nmodernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=\nmodernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=\nmodernc.org/cc/v3 v3.36.3 h1:uISP3F66UlixxWEcKuIWERa4TwrZENHSL8tWxZz8bHg=\nmodernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=\nmodernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=\nmodernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=\nmodernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=\nmodernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=\nmodernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws=\nmodernc.org/ccgo/v3 v3.16.9 h1:AXquSwg7GuMk11pIdw7fmO1Y/ybgazVkMhsZWCV0mHM=\nmodernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=\nmodernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=\nmodernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=\nmodernc.org/db v1.0.0 h1:2c6NdCfaLnshSvY7OU09cyAY0gYXUZj4lmg5ItHyucg=\nmodernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8=\nmodernc.org/file v1.0.0 h1:9/PdvjVxd5+LcWUQIfapAWRGOkDLK90rloa8s/au06A=\nmodernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw=\nmodernc.org/fileutil v1.0.0 h1:Z1AFLZwl6BO8A5NldQg/xTSjGLetp+1Ubvl4alfGx8w=\nmodernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=\nmodernc.org/golex v1.0.0 h1:wWpDlbK8ejRfSyi0frMyhilD3JBvtcx2AdGDnU+JtsE=\nmodernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=\nmodernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=\nmodernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=\nmodernc.org/internal v1.0.0 h1:XMDsFDcBDsibbBnHB2xzljZ+B1yrOVLEFkKL2u15Glw=\nmodernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM=\nmodernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=\nmodernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=\nmodernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU=\nmodernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=\nmodernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=\nmodernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=\nmodernc.org/libc v1.17.1 h1:Q8/Cpi36V/QBfuQaFVeisEBs3WqoGAJprZzmf7TfEYI=\nmodernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s=\nmodernc.org/lldb v1.0.0 h1:6vjDJxQEfhlOLwl4bhpwIz00uyFK4EmSYcbwqwbynsc=\nmodernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8=\nmodernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=\nmodernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=\nmodernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=\nmodernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=\nmodernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=\nmodernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=\nmodernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=\nmodernc.org/memory v1.2.1 h1:dkRh86wgmq/bJu2cAS2oqBCz/KsMZU7TUM4CibQ7eBs=\nmodernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=\nmodernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=\nmodernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=\nmodernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=\nmodernc.org/ql v1.0.0 h1:bIQ/trWNVjQPlinI6jdOQsi195SIturGo3mp5hsDqVU=\nmodernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY=\nmodernc.org/sortutil v1.1.0 h1:oP3U4uM+NT/qBQcbg/K2iqAX0Nx7B1b6YZtq3Gk/PjM=\nmodernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=\nmodernc.org/sqlite v1.18.1 h1:ko32eKt3jf7eqIkCgPAeHMBXw3riNSLhl2f3loEF7o8=\nmodernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=\nmodernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=\nmodernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=\nmodernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=\nmodernc.org/tcl v1.13.1 h1:npxzTwFTZYM8ghWicVIX1cRWzj7Nd8i6AqqX2p+IYao=\nmodernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=\nmodernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=\nmodernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=\nmodernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM=\nmodernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=\nmodernc.org/zappy v1.0.0 h1:dPVaP+3ueIUv4guk8PuZ2wiUGcJ1WUVvIheeSSTD0yk=\nmodernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\n"
  },
  {
    "path": "internal/cli/build_aws-s3.go",
    "content": "//go:build aws_s3\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/source/aws_s3\"\n)\n"
  },
  {
    "path": "internal/cli/build_bitbucket.go",
    "content": "//go:build bitbucket\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/source/bitbucket\"\n)\n"
  },
  {
    "path": "internal/cli/build_cassandra.go",
    "content": "//go:build cassandra\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/cassandra\"\n)\n"
  },
  {
    "path": "internal/cli/build_clickhouse.go",
    "content": "//go:build clickhouse\n\npackage cli\n\nimport (\n\t_ \"github.com/ClickHouse/clickhouse-go\"\n\t_ \"github.com/golang-migrate/migrate/v4/database/clickhouse\"\n)\n"
  },
  {
    "path": "internal/cli/build_cockroachdb.go",
    "content": "//go:build cockroachdb\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/cockroachdb\"\n)\n"
  },
  {
    "path": "internal/cli/build_firebird.go",
    "content": "//go:build firebird\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/firebird\"\n)\n"
  },
  {
    "path": "internal/cli/build_github.go",
    "content": "//go:build github\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/source/github\"\n)\n"
  },
  {
    "path": "internal/cli/build_github_ee.go",
    "content": "//go:build github\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/source/github_ee\"\n)\n"
  },
  {
    "path": "internal/cli/build_gitlab.go",
    "content": "//go:build gitlab\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/source/gitlab\"\n)\n"
  },
  {
    "path": "internal/cli/build_go-bindata.go",
    "content": "//go:build go_bindata\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/source/go_bindata\"\n)\n"
  },
  {
    "path": "internal/cli/build_godoc-vfs.go",
    "content": "//go:build godoc_vfs\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/source/godoc_vfs\"\n)\n"
  },
  {
    "path": "internal/cli/build_google-cloud-storage.go",
    "content": "//go:build google_cloud_storage\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/source/google_cloud_storage\"\n)\n"
  },
  {
    "path": "internal/cli/build_mongodb.go",
    "content": "//go:build mongodb\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/mongodb\"\n)\n"
  },
  {
    "path": "internal/cli/build_mysql.go",
    "content": "//go:build mysql\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/mysql\"\n)\n"
  },
  {
    "path": "internal/cli/build_neo4j.go",
    "content": "//go:build neo4j\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/neo4j\"\n)\n"
  },
  {
    "path": "internal/cli/build_pgx.go",
    "content": "//go:build pgx\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/pgx\"\n)\n"
  },
  {
    "path": "internal/cli/build_pgxv5.go",
    "content": "//go:build pgx5\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/pgx/v5\"\n)\n"
  },
  {
    "path": "internal/cli/build_postgres.go",
    "content": "//go:build postgres\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/postgres\"\n)\n"
  },
  {
    "path": "internal/cli/build_ql.go",
    "content": "//go:build ql\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/ql\"\n)\n"
  },
  {
    "path": "internal/cli/build_redshift.go",
    "content": "//go:build redshift\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/redshift\"\n)\n"
  },
  {
    "path": "internal/cli/build_rqlite.go",
    "content": "//go:build rqlite\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/rqlite\"\n)\n"
  },
  {
    "path": "internal/cli/build_snowflake.go",
    "content": "//go:build snowflake\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/snowflake\"\n)\n"
  },
  {
    "path": "internal/cli/build_spanner.go",
    "content": "//go:build spanner\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/spanner\"\n)\n"
  },
  {
    "path": "internal/cli/build_sqlcipher.go",
    "content": "//go:build sqlcipher\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/sqlcipher\"\n)\n"
  },
  {
    "path": "internal/cli/build_sqlite.go",
    "content": "//go:build sqlite\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/sqlite\"\n)\n"
  },
  {
    "path": "internal/cli/build_sqlite3.go",
    "content": "//go:build sqlite3\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/sqlite3\"\n)\n"
  },
  {
    "path": "internal/cli/build_sqlserver.go",
    "content": "//go:build sqlserver\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/sqlserver\"\n)\n"
  },
  {
    "path": "internal/cli/build_yugabytedb.go",
    "content": "//go:build yugabytedb\n\npackage cli\n\nimport (\n\t_ \"github.com/golang-migrate/migrate/v4/database/yugabytedb\"\n)\n"
  },
  {
    "path": "internal/cli/commands.go",
    "content": "package cli\n\nimport (\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/golang-migrate/migrate/v4\"\n\t_ \"github.com/golang-migrate/migrate/v4/database/stub\" // TODO remove again\n\t_ \"github.com/golang-migrate/migrate/v4/source/file\"\n)\n\nvar (\n\terrInvalidSequenceWidth     = errors.New(\"digits must be positive\")\n\terrIncompatibleSeqAndFormat = errors.New(\"the seq and format options are mutually exclusive\")\n\terrInvalidTimeFormat        = errors.New(\"time format may not be empty\")\n)\n\nfunc nextSeqVersion(matches []string, seqDigits int) (string, error) {\n\tif seqDigits <= 0 {\n\t\treturn \"\", errInvalidSequenceWidth\n\t}\n\n\tnextSeq := uint64(1)\n\n\tif len(matches) > 0 {\n\t\tfilename := matches[len(matches)-1]\n\t\tmatchSeqStr := filepath.Base(filename)\n\t\tidx := strings.Index(matchSeqStr, \"_\")\n\n\t\tif idx < 1 { // Using 1 instead of 0 since there should be at least 1 digit\n\t\t\treturn \"\", fmt.Errorf(\"malformed migration filename: %s\", filename)\n\t\t}\n\n\t\tvar err error\n\t\tmatchSeqStr = matchSeqStr[0:idx]\n\t\tnextSeq, err = strconv.ParseUint(matchSeqStr, 10, 64)\n\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tnextSeq++\n\t}\n\n\tversion := fmt.Sprintf(\"%0[2]*[1]d\", nextSeq, seqDigits)\n\n\tif len(version) > seqDigits {\n\t\treturn \"\", fmt.Errorf(\"next sequence number %s too large, at most %d digits are allowed\", version, seqDigits)\n\t}\n\n\treturn version, nil\n}\n\nfunc timeVersion(startTime time.Time, format string) (version string, err error) {\n\tswitch format {\n\tcase \"\":\n\t\terr = errInvalidTimeFormat\n\tcase \"unix\":\n\t\tversion = strconv.FormatInt(startTime.Unix(), 10)\n\tcase \"unixNano\":\n\t\tversion = strconv.FormatInt(startTime.UnixNano(), 10)\n\tdefault:\n\t\tversion = startTime.Format(format)\n\t}\n\n\treturn\n}\n\n// createCmd (meant to be called via a CLI command) creates a new migration\nfunc createCmd(dir string, startTime time.Time, format string, name string, ext string, seq bool, seqDigits int, print bool) error {\n\tif seq && format != defaultTimeFormat {\n\t\treturn errIncompatibleSeqAndFormat\n\t}\n\n\tvar version string\n\tvar err error\n\n\tdir = filepath.Clean(dir)\n\text = \".\" + strings.TrimPrefix(ext, \".\")\n\n\tif seq {\n\t\tmatches, err := filepath.Glob(filepath.Join(dir, \"*\"+ext))\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tversion, err = nextSeqVersion(matches, seqDigits)\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tversion, err = timeVersion(startTime, format)\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tversionGlob := filepath.Join(dir, version+\"_*\"+ext)\n\tmatches, err := filepath.Glob(versionGlob)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(matches) > 0 {\n\t\treturn fmt.Errorf(\"duplicate migration version: %s\", version)\n\t}\n\n\tif err = os.MkdirAll(dir, os.ModePerm); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, direction := range []string{\"up\", \"down\"} {\n\t\tbasename := fmt.Sprintf(\"%s_%s.%s%s\", version, name, direction, ext)\n\t\tfilename := filepath.Join(dir, basename)\n\n\t\tif err = createFile(filename); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif print {\n\t\t\tabsPath, _ := filepath.Abs(filename)\n\t\t\tlog.Println(absPath)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc createFile(filename string) error {\n\t// create exclusive (fails if file already exists)\n\t// os.Create() specifies 0666 as the FileMode, so we're doing the same\n\tf, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn f.Close()\n}\n\nfunc gotoCmd(m *migrate.Migrate, v uint) error {\n\tif err := m.Migrate(v); err != nil {\n\t\tif err != migrate.ErrNoChange {\n\t\t\treturn err\n\t\t}\n\t\tlog.Println(err)\n\t}\n\treturn nil\n}\n\nfunc upCmd(m *migrate.Migrate, limit int) error {\n\tif limit >= 0 {\n\t\tif err := m.Steps(limit); err != nil {\n\t\t\tif err != migrate.ErrNoChange {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tlog.Println(err)\n\t\t}\n\t} else {\n\t\tif err := m.Up(); err != nil {\n\t\t\tif err != migrate.ErrNoChange {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tlog.Println(err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc downCmd(m *migrate.Migrate, limit int) error {\n\tif limit >= 0 {\n\t\tif err := m.Steps(-limit); err != nil {\n\t\t\tif err != migrate.ErrNoChange {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tlog.Println(err)\n\t\t}\n\t} else {\n\t\tif err := m.Down(); err != nil {\n\t\t\tif err != migrate.ErrNoChange {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tlog.Println(err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc dropCmd(m *migrate.Migrate) error {\n\tif err := m.Drop(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc forceCmd(m *migrate.Migrate, v int) error {\n\tif err := m.Force(v); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc versionCmd(m *migrate.Migrate) error {\n\tv, dirty, err := m.Version()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif dirty {\n\t\tlog.Printf(\"%v (dirty)\\n\", v)\n\t} else {\n\t\tlog.Println(v)\n\t}\n\treturn nil\n}\n\n// numDownMigrationsFromArgs returns an int for number of migrations to apply\n// and a bool indicating if we need a confirm before applying\nfunc numDownMigrationsFromArgs(applyAll bool, args []string) (int, bool, error) {\n\tif applyAll {\n\t\tif len(args) > 0 {\n\t\t\treturn 0, false, errors.New(\"-all cannot be used with other arguments\")\n\t\t}\n\t\treturn -1, false, nil\n\t}\n\n\tswitch len(args) {\n\tcase 0:\n\t\treturn -1, true, nil\n\tcase 1:\n\t\tdownValue := args[0]\n\t\tn, err := strconv.ParseUint(downValue, 10, 64)\n\t\tif err != nil {\n\t\t\treturn 0, false, errors.New(\"can't read limit argument N\")\n\t\t}\n\t\treturn int(n), false, nil\n\tdefault:\n\t\treturn 0, false, errors.New(\"too many arguments\")\n\t}\n}\n"
  },
  {
    "path": "internal/cli/commands_test.go",
    "content": "package cli\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/suite\"\n)\n\ntype CreateCmdSuite struct {\n\tsuite.Suite\n}\n\nfunc TestCreateCmdSuite(t *testing.T) {\n\tsuite.Run(t, &CreateCmdSuite{})\n}\n\nfunc (s *CreateCmdSuite) mustCreateTempDir() string {\n\ttmpDir, err := os.MkdirTemp(\"\", \"migrate_\")\n\n\tif err != nil {\n\t\ts.FailNow(err.Error())\n\t}\n\n\treturn tmpDir\n}\n\nfunc (s *CreateCmdSuite) mustCreateDir(dir string) {\n\tif err := os.MkdirAll(dir, 0755); err != nil {\n\t\ts.FailNow(err.Error())\n\t}\n}\n\nfunc (s *CreateCmdSuite) mustRemoveDir(dir string) {\n\tif err := os.RemoveAll(dir); err != nil {\n\t\ts.FailNow(err.Error())\n\t}\n}\n\nfunc (s *CreateCmdSuite) mustWriteFile(dir, file, body string) {\n\tif err := os.WriteFile(filepath.Join(dir, file), []byte(body), 0644); err != nil {\n\t\ts.FailNow(err.Error())\n\t}\n}\n\nfunc (s *CreateCmdSuite) mustGetwd() string {\n\tcwd, err := os.Getwd()\n\n\tif err != nil {\n\t\ts.FailNow(err.Error())\n\t}\n\n\treturn cwd\n}\n\nfunc (s *CreateCmdSuite) mustChdir(dir string) {\n\tif err := os.Chdir(dir); err != nil {\n\t\ts.FailNow(err.Error())\n\t}\n}\n\nfunc (s *CreateCmdSuite) assertEmptyDir(dir string) bool {\n\tfis, err := os.ReadDir(dir)\n\n\tif err != nil {\n\t\treturn s.Fail(err.Error())\n\t}\n\n\treturn s.Empty(fis)\n}\n\nfunc (s *CreateCmdSuite) TestNextSeqVersion() {\n\tcases := []struct {\n\t\ttid         string\n\t\tmatches     []string\n\t\tseqDigits   int\n\t\texpected    string\n\t\texpectedErr error\n\t}{\n\t\t{\"Bad digits\", []string{}, 0, \"\", errInvalidSequenceWidth},\n\t\t{\"Single digit initialize\", []string{}, 1, \"1\", nil},\n\t\t{\"Single digit malformed\", []string{\"bad\"}, 1, \"\", errors.New(\"malformed migration filename: bad\")},\n\t\t{\"Single digit no int\", []string{\"bad_bad\"}, 1, \"\", errors.New(`strconv.ParseUint: parsing \"bad\": invalid syntax`)},\n\t\t{\"Single digit negative seq\", []string{\"-5_test\"}, 1, \"\", errors.New(`strconv.ParseUint: parsing \"-5\": invalid syntax`)},\n\t\t{\"Single digit increment\", []string{\"3_test\", \"4_test\"}, 1, \"5\", nil},\n\t\t{\"Single digit overflow\", []string{\"9_test\"}, 1, \"\", errors.New(\"next sequence number 10 too large, at most 1 digits are allowed\")},\n\t\t{\"Zero-pad initialize\", []string{}, 6, \"000001\", nil},\n\t\t{\"Zero-pad malformed\", []string{\"bad\"}, 6, \"\", errors.New(\"malformed migration filename: bad\")},\n\t\t{\"Zero-pad no int\", []string{\"bad_bad\"}, 6, \"\", errors.New(`strconv.ParseUint: parsing \"bad\": invalid syntax`)},\n\t\t{\"Zero-pad negative seq\", []string{\"-000005_test\"}, 6, \"\", errors.New(`strconv.ParseUint: parsing \"-000005\": invalid syntax`)},\n\t\t{\"Zero-pad increment\", []string{\"000003_test\", \"000004_test\"}, 6, \"000005\", nil},\n\t\t{\"Zero-pad overflow\", []string{\"999999_test\"}, 6, \"\", errors.New(\"next sequence number 1000000 too large, at most 6 digits are allowed\")},\n\t\t{\"dir absolute path\", []string{\"/migrationDir/000001_test\"}, 6, \"000002\", nil},\n\t\t{\"dir relative path\", []string{\"migrationDir/000001_test\"}, 6, \"000002\", nil},\n\t\t{\"dir dot prefix\", []string{\"./migrationDir/000001_test\"}, 6, \"000002\", nil},\n\t\t{\"dir parent prefix\", []string{\"../migrationDir/000001_test\"}, 6, \"000002\", nil},\n\t\t{\"dir no prefix\", []string{\"000001_test\"}, 6, \"000002\", nil},\n\t}\n\n\tfor _, c := range cases {\n\t\ts.Run(c.tid, func() {\n\t\t\tv, err := nextSeqVersion(c.matches, c.seqDigits)\n\n\t\t\tif c.expectedErr != nil {\n\t\t\t\ts.EqualError(err, c.expectedErr.Error())\n\t\t\t} else {\n\t\t\t\ts.NoError(err)\n\t\t\t\ts.Equal(c.expected, v)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc (s *CreateCmdSuite) TestTimeVersion() {\n\tts := time.Date(2000, 12, 25, 00, 01, 02, 3456789, time.UTC)\n\ttsUnixStr := strconv.FormatInt(ts.Unix(), 10)\n\ttsUnixNanoStr := strconv.FormatInt(ts.UnixNano(), 10)\n\n\tcases := []struct {\n\t\ttid         string\n\t\ttime        time.Time\n\t\tformat      string\n\t\texpected    string\n\t\texpectedErr error\n\t}{\n\t\t{\"Bad format\", ts, \"\", \"\", errInvalidTimeFormat},\n\t\t{\"unix\", ts, \"unix\", tsUnixStr, nil},\n\t\t{\"unixNano\", ts, \"unixNano\", tsUnixNanoStr, nil},\n\t\t{\"custom ymthms\", ts, \"20060102150405\", \"20001225000102\", nil},\n\t}\n\n\tfor _, c := range cases {\n\t\ts.Run(c.tid, func() {\n\t\t\tv, err := timeVersion(c.time, c.format)\n\n\t\t\tif c.expectedErr != nil {\n\t\t\t\ts.EqualError(err, c.expectedErr.Error())\n\t\t\t} else {\n\t\t\t\ts.NoError(err)\n\t\t\t\ts.Equal(c.expected, v)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestCreateCmd tests function createCmd.\n//\n// For each test case, it creates a temp dir as \"sandbox\" (called `baseDir`) and\n// all path manipulations are relative to `baseDir`.\nfunc (s *CreateCmdSuite) TestCreateCmd() {\n\tts := time.Date(2000, 12, 25, 00, 01, 02, 3456789, time.UTC)\n\ttsUnixStr := strconv.FormatInt(ts.Unix(), 10)\n\ttsUnixNanoStr := strconv.FormatInt(ts.UnixNano(), 10)\n\ttestCwd := s.mustGetwd()\n\n\tcases := []struct {\n\t\ttid           string\n\t\texistingDirs  []string // directory paths to create before test. relative to baseDir.\n\t\tcwd           string   // path to chdir to before test. relative to baseDir.\n\t\texistingFiles []string // file paths created before test. relative to baseDir.\n\t\texpectedFiles []string // file paths expected to exist after test. paths relative to baseDir.\n\t\texpectedErr   error\n\t\tdir           string // `dir` parameter. if absolute path, will be converted to baseDir/dir.\n\t\tstartTime     time.Time\n\t\tformat        string\n\t\tseq           bool\n\t\tseqDigits     int\n\t\text           string\n\t\tname          string\n\t}{\n\t\t{\"seq and format\", nil, \"\", nil, nil, errIncompatibleSeqAndFormat, \".\", ts, \"unix\", true, 4, \"sql\", \"name\"},\n\t\t{\"seq init dir dot\", nil, \"\", nil, []string{\"0001_name.up.sql\", \"0001_name.down.sql\"}, nil, \".\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq init dir dot trailing slash\", nil, \"\", nil, []string{\"0001_name.up.sql\", \"0001_name.down.sql\"}, nil, \"./\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq init dir double dot\", []string{\"subdir\"}, \"subdir\", nil, []string{\"0001_name.up.sql\", \"0001_name.down.sql\"}, nil, \"..\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq init dir double dot trailing slash\", []string{\"subdir\"}, \"subdir\", nil, []string{\"0001_name.up.sql\", \"0001_name.down.sql\"}, nil, \"../\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq init dir absolute\", []string{\"subdir\"}, \"\", nil, []string{\"subdir/0001_name.up.sql\", \"subdir/0001_name.down.sql\"}, nil, \"/subdir\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq init dir absolute trailing slash\", []string{\"subdir\"}, \"\", nil, []string{\"subdir/0001_name.up.sql\", \"subdir/0001_name.down.sql\"}, nil, \"/subdir/\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq init dir relative\", []string{\"subdir\"}, \"\", nil, []string{\"subdir/0001_name.up.sql\", \"subdir/0001_name.down.sql\"}, nil, \"subdir\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq init dir relative trailing slash\", []string{\"subdir\"}, \"\", nil, []string{\"subdir/0001_name.up.sql\", \"subdir/0001_name.down.sql\"}, nil, \"subdir/\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq init dir dot relative\", []string{\"subdir\"}, \"\", nil, []string{\"subdir/0001_name.up.sql\", \"subdir/0001_name.down.sql\"}, nil, \"./subdir\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq init dir dot relative trailing slash\", []string{\"subdir\"}, \"\", nil, []string{\"subdir/0001_name.up.sql\", \"subdir/0001_name.down.sql\"}, nil, \"./subdir/\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq init dir double dot relative\", []string{\"subdir\"}, \"subdir\", nil, []string{\"subdir/0001_name.up.sql\", \"subdir/0001_name.down.sql\"}, nil, \"../subdir\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq init dir double dot relative trailing slash\", []string{\"subdir\"}, \"subdir\", nil, []string{\"subdir/0001_name.up.sql\", \"subdir/0001_name.down.sql\"}, nil, \"../subdir/\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq init dir maze\", []string{\"subdir\"}, \"subdir\", nil, []string{\"0001_name.up.sql\", \"0001_name.down.sql\"}, nil, \"..//subdir/./.././/subdir/..\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq width invalid\", nil, \"\", nil, nil, errInvalidSequenceWidth, \".\", ts, defaultTimeFormat, true, 0, \"sql\", \"name\"},\n\t\t{\"seq malformed\", nil, \"\", []string{\"bad.sql\"}, []string{\"bad.sql\"}, errors.New(\"malformed migration filename: bad.sql\"), \".\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq not int\", nil, \"\", []string{\"bad_bad.sql\"}, []string{\"bad_bad.sql\"}, errors.New(`strconv.ParseUint: parsing \"bad\": invalid syntax`), \".\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq negative\", nil, \"\", []string{\"-5_negative.sql\"}, []string{\"-5_negative.sql\"}, errors.New(`strconv.ParseUint: parsing \"-5\": invalid syntax`), \".\", ts, defaultTimeFormat, true, 4, \"sql\", \"name\"},\n\t\t{\"seq increment\", nil, \"\", []string{\"3_three.sql\", \"4_four.sql\"}, []string{\"3_three.sql\", \"4_four.sql\", \"0005_five.up.sql\", \"0005_five.down.sql\"}, nil, \".\", ts, defaultTimeFormat, true, 4, \"sql\", \"five\"},\n\t\t{\"seq overflow\", nil, \"\", []string{\"9_nine.sql\"}, []string{\"9_nine.sql\"}, errors.New(`next sequence number 10 too large, at most 1 digits are allowed`), \".\", ts, defaultTimeFormat, true, 1, \"sql\", \"ten\"},\n\t\t{\"time empty format\", nil, \"\", nil, nil, errInvalidTimeFormat, \".\", ts, \"\", false, 0, \"sql\", \"name\"},\n\t\t{\"time unix\", nil, \"\", nil, []string{tsUnixStr + \"_name.up.sql\", tsUnixStr + \"_name.down.sql\"}, nil, \".\", ts, \"unix\", false, 0, \"sql\", \"name\"},\n\t\t{\"time unixNano\", nil, \"\", nil, []string{tsUnixNanoStr + \"_name.up.sql\", tsUnixNanoStr + \"_name.down.sql\"}, nil, \".\", ts, \"unixNano\", false, 0, \"sql\", \"name\"},\n\t\t{\"time custom format\", nil, \"\", nil, []string{\"20001225000102_name.up.sql\", \"20001225000102_name.down.sql\"}, nil, \".\", ts, \"20060102150405\", false, 0, \"sql\", \"name\"},\n\t\t{\"time version collision\", nil, \"\", []string{\"20001225_name.up.sql\", \"20001225_name.down.sql\"}, []string{\"20001225_name.up.sql\", \"20001225_name.down.sql\"}, errors.New(\"duplicate migration version: 20001225\"), \".\", ts, \"20060102\", false, 0, \"sql\", \"name\"},\n\t\t{\"dir invalid\", nil, \"\", []string{\"file\"}, []string{\"file\"}, errors.New(\"mkdir 'test: this is invalid dir name'\\x00: invalid argument\"), \"'test: this is invalid dir name'\\000\", ts, \"unix\", false, 0, \"sql\", \"name\"},\n\t}\n\n\tfor _, c := range cases {\n\t\ts.Run(c.tid, func() {\n\t\t\tbaseDir := s.mustCreateTempDir()\n\n\t\t\tfor _, d := range c.existingDirs {\n\t\t\t\ts.mustCreateDir(filepath.Join(baseDir, d))\n\t\t\t}\n\n\t\t\tcwd := baseDir\n\n\t\t\tif c.cwd != \"\" {\n\t\t\t\tcwd = filepath.Join(baseDir, c.cwd)\n\t\t\t}\n\n\t\t\ts.mustChdir(cwd)\n\n\t\t\tfor _, f := range c.existingFiles {\n\t\t\t\ts.mustWriteFile(baseDir, f, \"\")\n\t\t\t}\n\n\t\t\tdir := c.dir\n\t\t\tdir = filepath.ToSlash(dir)\n\t\t\tvolName := filepath.VolumeName(baseDir)\n\t\t\t// Windows specific, can not recognize \\subdir as abs path\n\t\t\tisWindowsAbsPathNoLetter := strings.HasPrefix(dir, \"/\") && volName != \"\"\n\t\t\tisRealAbsPath := filepath.IsAbs(dir)\n\t\t\tif isWindowsAbsPathNoLetter || isRealAbsPath {\n\t\t\t\tdir = filepath.Join(baseDir, dir)\n\t\t\t}\n\n\t\t\terr := createCmd(dir, c.startTime, c.format, c.name, c.ext, c.seq, c.seqDigits, false)\n\n\t\t\tif c.expectedErr != nil {\n\t\t\t\ts.EqualError(err, c.expectedErr.Error())\n\t\t\t} else {\n\t\t\t\ts.NoError(err)\n\t\t\t}\n\n\t\t\tif len(c.expectedFiles) == 0 {\n\t\t\t\ts.assertEmptyDir(baseDir)\n\t\t\t} else {\n\t\t\t\tfor _, f := range c.expectedFiles {\n\t\t\t\t\ts.FileExists(filepath.Join(baseDir, f))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ts.mustChdir(testCwd)\n\t\t\ts.mustRemoveDir(baseDir)\n\t\t})\n\t}\n}\n\nfunc TestNumDownFromArgs(t *testing.T) {\n\tcases := []struct {\n\t\tname                string\n\t\targs                []string\n\t\tapplyAll            bool\n\t\texpectedNeedConfirm bool\n\t\texpectedNum         int\n\t\texpectedErrStr      string\n\t}{\n\t\t{\"no args\", []string{}, false, true, -1, \"\"},\n\t\t{\"down all\", []string{}, true, false, -1, \"\"},\n\t\t{\"down 5\", []string{\"5\"}, false, false, 5, \"\"},\n\t\t{\"down N\", []string{\"N\"}, false, false, 0, \"can't read limit argument N\"},\n\t\t{\"extra arg after -all\", []string{\"5\"}, true, false, 0, \"-all cannot be used with other arguments\"},\n\t\t{\"extra arg before -all\", []string{\"5\", \"-all\"}, false, false, 0, \"too many arguments\"},\n\t}\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tnum, needsConfirm, err := numDownMigrationsFromArgs(c.applyAll, c.args)\n\t\t\tif needsConfirm != c.expectedNeedConfirm {\n\t\t\t\tt.Errorf(\"Incorrect needsConfirm was: %v wanted %v\", needsConfirm, c.expectedNeedConfirm)\n\t\t\t}\n\n\t\t\tif num != c.expectedNum {\n\t\t\t\tt.Errorf(\"Incorrect num was: %v wanted %v\", num, c.expectedNum)\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tif err.Error() != c.expectedErrStr {\n\t\t\t\t\tt.Error(\"Incorrect error: \" + err.Error() + \" != \" + c.expectedErrStr)\n\t\t\t\t}\n\t\t\t} else if c.expectedErrStr != \"\" {\n\t\t\t\tt.Error(\"Expected error: \" + c.expectedErrStr + \" but got nil instead\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/cli/log.go",
    "content": "package cli\n\nimport (\n\t\"fmt\"\n\tlogpkg \"log\"\n\t\"os\"\n)\n\n// Log represents the logger\ntype Log struct {\n\tverbose bool\n}\n\n// Printf prints out formatted string into a log\nfunc (l *Log) Printf(format string, v ...interface{}) {\n\tif l.verbose {\n\t\tlogpkg.Printf(format, v...)\n\t} else {\n\t\tfmt.Fprintf(os.Stderr, format, v...)\n\t}\n}\n\n// Println prints out args into a log\nfunc (l *Log) Println(args ...interface{}) {\n\tif l.verbose {\n\t\tlogpkg.Println(args...)\n\t} else {\n\t\tfmt.Fprintln(os.Stderr, args...)\n\t}\n}\n\n// Verbose shows if verbose print enabled\nfunc (l *Log) Verbose() bool {\n\treturn l.verbose\n}\n\nfunc (l *Log) fatal(args ...interface{}) {\n\tl.Println(args...)\n\tos.Exit(1)\n}\n\nfunc (l *Log) fatalErr(err error) {\n\tl.fatal(\"error:\", err)\n}\n"
  },
  {
    "path": "internal/cli/main.go",
    "content": "package cli\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t\"github.com/golang-migrate/migrate/v4/source\"\n)\n\nconst (\n\tdefaultTimeFormat = \"20060102150405\"\n\tdefaultTimezone   = \"UTC\"\n\tcreateUsage       = `create [-ext E] [-dir D] [-seq] [-digits N] [-format] [-tz] NAME\n\t   Create a set of timestamped up/down migrations titled NAME, in directory D with extension E.\n\t   Use -seq option to generate sequential up/down migrations with N digits.\n\t   Use -format option to specify a Go time format string. Note: migrations with the same time cause \"duplicate migration version\" error.\n           Use -tz option to specify the timezone that will be used when generating non-sequential migrations (defaults: UTC).\n`\n\tgotoUsage = `goto V       Migrate to version V`\n\tupUsage   = `up [N]       Apply all or N up migrations`\n\tdownUsage = `down [N] [-all]    Apply all or N down migrations\n\tUse -all to apply all down migrations`\n\tdropUsage = `drop [-f]    Drop everything inside database\n\tUse -f to bypass confirmation`\n\tforceUsage = `force V      Set version V but don't run migration (ignores dirty state)`\n)\n\nfunc handleSubCmdHelp(help bool, usage string, flagSet *flag.FlagSet) {\n\tif help {\n\t\tfmt.Fprintln(os.Stderr, usage)\n\t\tflagSet.PrintDefaults()\n\t\tos.Exit(0)\n\t}\n}\n\nfunc newFlagSetWithHelp(name string) (*flag.FlagSet, *bool) {\n\tflagSet := flag.NewFlagSet(name, flag.ExitOnError)\n\thelpPtr := flagSet.Bool(\"help\", false, \"Print help information\")\n\treturn flagSet, helpPtr\n}\n\n// set main log\nvar log = &Log{}\n\nfunc printUsageAndExit() {\n\tflag.Usage()\n\n\t// If a command is not found we exit with a status 2 to match the behavior\n\t// of flag.Parse() with flag.ExitOnError when parsing an invalid flag.\n\tos.Exit(2)\n}\n\n// Main function of a cli application. It is public for backwards compatibility with `cli` package\nfunc Main(version string) {\n\thelpPtr := flag.Bool(\"help\", false, \"\")\n\tversionPtr := flag.Bool(\"version\", false, \"\")\n\tverbosePtr := flag.Bool(\"verbose\", false, \"\")\n\tprefetchPtr := flag.Uint(\"prefetch\", 10, \"\")\n\tlockTimeoutPtr := flag.Uint(\"lock-timeout\", 15, \"\")\n\tpathPtr := flag.String(\"path\", \"\", \"\")\n\tdatabasePtr := flag.String(\"database\", \"\", \"\")\n\tsourcePtr := flag.String(\"source\", \"\", \"\")\n\n\tflag.Usage = func() {\n\t\tfmt.Fprintf(os.Stderr,\n\t\t\t`Usage: migrate OPTIONS COMMAND [arg...]\n       migrate [ -version | -help ]\n\nOptions:\n  -source          Location of the migrations (driver://url)\n  -path            Shorthand for -source=file://path\n  -database        Run migrations against this database (driver://url)\n  -prefetch N      Number of migrations to load in advance before executing (default 10)\n  -lock-timeout N  Allow N seconds to acquire database lock (default 15)\n  -verbose         Print verbose logging\n  -version         Print version\n  -help            Print usage\n\nCommands:\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n  version      Print current migration version\n\nSource drivers: `+strings.Join(source.List(), \", \")+`\nDatabase drivers: `+strings.Join(database.List(), \", \")+\"\\n\", createUsage, gotoUsage, upUsage, downUsage, dropUsage, forceUsage)\n\t}\n\n\tflag.Parse()\n\n\t// initialize logger\n\tlog.verbose = *verbosePtr\n\n\t// show cli version\n\tif *versionPtr {\n\t\tfmt.Fprintln(os.Stderr, version)\n\t\tos.Exit(0)\n\t}\n\n\t// show help\n\tif *helpPtr {\n\t\tflag.Usage()\n\t\tos.Exit(0)\n\t}\n\n\t// translate -path into -source if given\n\tif *sourcePtr == \"\" && *pathPtr != \"\" {\n\t\t*sourcePtr = fmt.Sprintf(\"file://%v\", *pathPtr)\n\t}\n\n\t// initialize migrate\n\t// don't catch migraterErr here and let each command decide\n\t// how it wants to handle the error\n\tmigrater, migraterErr := migrate.New(*sourcePtr, *databasePtr)\n\tdefer func() {\n\t\tif migraterErr == nil {\n\t\t\tif _, err := migrater.Close(); err != nil {\n\t\t\t\tlog.Println(err)\n\t\t\t}\n\t\t}\n\t}()\n\tif migraterErr == nil {\n\t\tmigrater.Log = log\n\t\tmigrater.PrefetchMigrations = *prefetchPtr\n\t\tmigrater.LockTimeout = time.Duration(int64(*lockTimeoutPtr)) * time.Second\n\n\t\t// handle Ctrl+c\n\t\tsignals := make(chan os.Signal, 1)\n\t\tsignal.Notify(signals, syscall.SIGINT)\n\t\tgo func() {\n\t\t\tfor range signals {\n\t\t\t\tlog.Println(\"Stopping after this running migration ...\")\n\t\t\t\tmigrater.GracefulStop <- true\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\t}\n\n\tstartTime := time.Now()\n\n\tif len(flag.Args()) < 1 {\n\t\tprintUsageAndExit()\n\t}\n\targs := flag.Args()[1:]\n\n\tswitch flag.Arg(0) {\n\tcase \"create\":\n\n\t\tseq := false\n\t\tseqDigits := 6\n\n\t\tcreateFlagSet, help := newFlagSetWithHelp(\"create\")\n\t\textPtr := createFlagSet.String(\"ext\", \"\", \"File extension\")\n\t\tdirPtr := createFlagSet.String(\"dir\", \"\", \"Directory to place file in (default: current working directory)\")\n\t\tformatPtr := createFlagSet.String(\"format\", defaultTimeFormat, `The Go time format string to use. If the string \"unix\" or \"unixNano\" is specified, then the seconds or nanoseconds since January 1, 1970 UTC respectively will be used. Caution, due to the behavior of time.Time.Format(), invalid format strings will not error`)\n\t\ttimezoneName := createFlagSet.String(\"tz\", defaultTimezone, `The timezone that will be used for generating timestamps (default: utc)`)\n\t\tcreateFlagSet.BoolVar(&seq, \"seq\", seq, \"Use sequential numbers instead of timestamps (default: false)\")\n\t\tcreateFlagSet.IntVar(&seqDigits, \"digits\", seqDigits, \"The number of digits to use in sequences (default: 6)\")\n\n\t\tif err := createFlagSet.Parse(args); err != nil {\n\t\t\tlog.fatalErr(err)\n\t\t}\n\n\t\thandleSubCmdHelp(*help, createUsage, createFlagSet)\n\n\t\tif createFlagSet.NArg() == 0 {\n\t\t\tlog.fatal(\"error: please specify name\")\n\t\t}\n\t\tname := createFlagSet.Arg(0)\n\n\t\tif *extPtr == \"\" {\n\t\t\tlog.fatal(\"error: -ext flag must be specified\")\n\t\t}\n\n\t\ttimezone, err := time.LoadLocation(*timezoneName)\n\t\tif err != nil {\n\t\t\tlog.fatal(err)\n\t\t}\n\n\t\tif err := createCmd(*dirPtr, startTime.In(timezone), *formatPtr, name, *extPtr, seq, seqDigits, true); err != nil {\n\t\t\tlog.fatalErr(err)\n\t\t}\n\n\tcase \"goto\":\n\n\t\tgotoSet, helpPtr := newFlagSetWithHelp(\"goto\")\n\n\t\tif err := gotoSet.Parse(args); err != nil {\n\t\t\tlog.fatalErr(err)\n\t\t}\n\n\t\thandleSubCmdHelp(*helpPtr, gotoUsage, gotoSet)\n\n\t\tif migraterErr != nil {\n\t\t\tlog.fatalErr(migraterErr)\n\t\t}\n\n\t\tif gotoSet.NArg() == 0 {\n\t\t\tlog.fatal(\"error: please specify version argument V\")\n\t\t}\n\n\t\tv, err := strconv.ParseUint(gotoSet.Arg(0), 10, 64)\n\t\tif err != nil {\n\t\t\tlog.fatal(\"error: can't read version argument V\")\n\t\t}\n\n\t\tif err := gotoCmd(migrater, uint(v)); err != nil {\n\t\t\tlog.fatalErr(err)\n\t\t}\n\n\t\tif log.verbose {\n\t\t\tlog.Println(\"Finished after\", time.Since(startTime))\n\t\t}\n\n\tcase \"up\":\n\t\tupSet, helpPtr := newFlagSetWithHelp(\"up\")\n\n\t\tif err := upSet.Parse(args); err != nil {\n\t\t\tlog.fatalErr(err)\n\t\t}\n\n\t\thandleSubCmdHelp(*helpPtr, upUsage, upSet)\n\n\t\tif migraterErr != nil {\n\t\t\tlog.fatalErr(migraterErr)\n\t\t}\n\n\t\tlimit := -1\n\t\tif upSet.NArg() > 0 {\n\t\t\tn, err := strconv.ParseUint(upSet.Arg(0), 10, 64)\n\t\t\tif err != nil {\n\t\t\t\tlog.fatal(\"error: can't read limit argument N\")\n\t\t\t}\n\t\t\tlimit = int(n)\n\t\t}\n\n\t\tif err := upCmd(migrater, limit); err != nil {\n\t\t\tlog.fatalErr(err)\n\t\t}\n\n\t\tif log.verbose {\n\t\t\tlog.Println(\"Finished after\", time.Since(startTime))\n\t\t}\n\n\tcase \"down\":\n\t\tdownFlagSet, helpPtr := newFlagSetWithHelp(\"down\")\n\t\tapplyAll := downFlagSet.Bool(\"all\", false, \"Apply all down migrations\")\n\n\t\tif err := downFlagSet.Parse(args); err != nil {\n\t\t\tlog.fatalErr(err)\n\t\t}\n\n\t\thandleSubCmdHelp(*helpPtr, downUsage, downFlagSet)\n\n\t\tif migraterErr != nil {\n\t\t\tlog.fatalErr(migraterErr)\n\t\t}\n\n\t\tdownArgs := downFlagSet.Args()\n\t\tnum, needsConfirm, err := numDownMigrationsFromArgs(*applyAll, downArgs)\n\t\tif err != nil {\n\t\t\tlog.fatalErr(err)\n\t\t}\n\t\tif needsConfirm {\n\t\t\tlog.Println(\"Are you sure you want to apply all down migrations? [y/N]\")\n\t\t\tvar response string\n\t\t\t_, _ = fmt.Scanln(&response)\n\t\t\tresponse = strings.ToLower(strings.TrimSpace(response))\n\n\t\t\tif response == \"y\" {\n\t\t\t\tlog.Println(\"Applying all down migrations\")\n\t\t\t} else {\n\t\t\t\tlog.fatal(\"Not applying all down migrations\")\n\t\t\t}\n\t\t}\n\n\t\tif err := downCmd(migrater, num); err != nil {\n\t\t\tlog.fatalErr(err)\n\t\t}\n\n\t\tif log.verbose {\n\t\t\tlog.Println(\"Finished after\", time.Since(startTime))\n\t\t}\n\n\tcase \"drop\":\n\t\tdropFlagSet, help := newFlagSetWithHelp(\"drop\")\n\t\tforceDrop := dropFlagSet.Bool(\"f\", false, \"Force the drop command by bypassing the confirmation prompt\")\n\n\t\tif err := dropFlagSet.Parse(args); err != nil {\n\t\t\tlog.fatalErr(err)\n\t\t}\n\n\t\thandleSubCmdHelp(*help, dropUsage, dropFlagSet)\n\n\t\tif !*forceDrop {\n\t\t\tlog.Println(\"Are you sure you want to drop the entire database schema? [y/N]\")\n\t\t\tvar response string\n\t\t\t_, _ = fmt.Scanln(&response)\n\t\t\tresponse = strings.ToLower(strings.TrimSpace(response))\n\n\t\t\tif response == \"y\" {\n\t\t\t\tlog.Println(\"Dropping the entire database schema\")\n\t\t\t} else {\n\t\t\t\tlog.fatal(\"Aborted dropping the entire database schema\")\n\t\t\t}\n\t\t}\n\n\t\tif migraterErr != nil {\n\t\t\tlog.fatalErr(migraterErr)\n\t\t}\n\n\t\tif err := dropCmd(migrater); err != nil {\n\t\t\tlog.fatalErr(err)\n\t\t}\n\n\t\tif log.verbose {\n\t\t\tlog.Println(\"Finished after\", time.Since(startTime))\n\t\t}\n\n\tcase \"force\":\n\t\tforceSet, helpPtr := newFlagSetWithHelp(\"force\")\n\n\t\tif err := forceSet.Parse(args); err != nil {\n\t\t\tlog.fatalErr(err)\n\t\t}\n\n\t\thandleSubCmdHelp(*helpPtr, forceUsage, forceSet)\n\n\t\tif migraterErr != nil {\n\t\t\tlog.fatalErr(migraterErr)\n\t\t}\n\n\t\tif forceSet.NArg() == 0 {\n\t\t\tlog.fatal(\"error: please specify version argument V\")\n\t\t}\n\n\t\tv, err := strconv.ParseInt(forceSet.Arg(0), 10, 64)\n\t\tif err != nil {\n\t\t\tlog.fatal(\"error: can't read version argument V\")\n\t\t}\n\n\t\tif v < -1 {\n\t\t\tlog.fatal(\"error: argument V must be >= -1\")\n\t\t}\n\n\t\tif err := forceCmd(migrater, int(v)); err != nil {\n\t\t\tlog.fatalErr(err)\n\t\t}\n\n\t\tif log.verbose {\n\t\t\tlog.Println(\"Finished after\", time.Since(startTime))\n\t\t}\n\n\tcase \"version\":\n\t\tif migraterErr != nil {\n\t\t\tlog.fatalErr(migraterErr)\n\t\t}\n\n\t\tif err := versionCmd(migrater); err != nil {\n\t\t\tlog.fatalErr(err)\n\t\t}\n\n\tdefault:\n\t\tprintUsageAndExit()\n\t}\n}\n"
  },
  {
    "path": "internal/url/url.go",
    "content": "package url\n\nimport (\n\t\"errors\"\n\t\"strings\"\n)\n\nvar errNoScheme = errors.New(\"no scheme\")\nvar errEmptyURL = errors.New(\"URL cannot be empty\")\n\n// schemeFromURL returns the scheme from a URL string\nfunc SchemeFromURL(url string) (string, error) {\n\tif url == \"\" {\n\t\treturn \"\", errEmptyURL\n\t}\n\n\ti := strings.Index(url, \":\")\n\n\t// No : or : is the first character.\n\tif i < 1 {\n\t\treturn \"\", errNoScheme\n\t}\n\n\treturn url[0:i], nil\n}\n"
  },
  {
    "path": "internal/url/url_test.go",
    "content": "package url\n\nimport (\n\t\"testing\"\n)\n\nfunc TestSchemeFromUrl(t *testing.T) {\n\tcases := []struct {\n\t\tname      string\n\t\turlStr    string\n\t\texpected  string\n\t\texpectErr error\n\t}{\n\t\t{\n\t\t\tname:     \"Simple\",\n\t\t\turlStr:   \"protocol://path\",\n\t\t\texpected: \"protocol\",\n\t\t},\n\t\t{\n\t\t\t// See issue #264\n\t\t\tname:     \"MySQLWithPort\",\n\t\t\turlStr:   \"mysql://user:pass@tcp(host:1337)/db\",\n\t\t\texpected: \"mysql\",\n\t\t},\n\t\t{\n\t\t\tname:      \"Empty\",\n\t\t\turlStr:    \"\",\n\t\t\texpectErr: errEmptyURL,\n\t\t},\n\t\t{\n\t\t\tname:      \"NoScheme\",\n\t\t\turlStr:    \"hello\",\n\t\t\texpectErr: errNoScheme,\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ts, err := SchemeFromURL(tc.urlStr)\n\t\t\tif err != tc.expectErr {\n\t\t\t\tt.Fatalf(\"expected %q, but received %q\", tc.expectErr, err)\n\t\t\t}\n\t\t\tif s != tc.expected {\n\t\t\t\tt.Fatalf(\"expected %q, but received %q\", tc.expected, s)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "log.go",
    "content": "package migrate\n\n// Logger is an interface so you can pass in your own\n// logging implementation.\ntype Logger interface {\n\n\t// Printf is like fmt.Printf\n\tPrintf(format string, v ...interface{})\n\n\t// Verbose should return true when verbose logging output is wanted\n\tVerbose() bool\n}\n"
  },
  {
    "path": "migrate.go",
    "content": "// Package migrate reads migrations from sources and runs them against databases.\n// Sources are defined by the `source.Driver` and databases by the `database.Driver`\n// interface. The driver interfaces are kept \"dumb\", all migration logic is kept\n// in this package.\npackage migrate\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\tiurl \"github.com/golang-migrate/migrate/v4/internal/url\"\n\t\"github.com/golang-migrate/migrate/v4/source\"\n)\n\n// DefaultPrefetchMigrations sets the number of migrations to pre-read\n// from the source. This is helpful if the source is remote, but has little\n// effect for a local source (i.e. file system).\n// Please note that this setting has a major impact on the memory usage,\n// since each pre-read migration is buffered in memory. See DefaultBufferSize.\nvar DefaultPrefetchMigrations = uint(10)\n\n// DefaultLockTimeout sets the max time a database driver has to acquire a lock.\nvar DefaultLockTimeout = 15 * time.Second\n\nvar (\n\tErrNoChange       = errors.New(\"no change\")\n\tErrNilVersion     = errors.New(\"no migration\")\n\tErrInvalidVersion = errors.New(\"version must be >= -1\")\n\tErrLocked         = errors.New(\"database locked\")\n\tErrLockTimeout    = errors.New(\"timeout: can't acquire database lock\")\n)\n\n// ErrShortLimit is an error returned when not enough migrations\n// can be returned by a source for a given limit.\ntype ErrShortLimit struct {\n\tShort uint\n}\n\n// Error implements the error interface.\nfunc (e ErrShortLimit) Error() string {\n\treturn fmt.Sprintf(\"limit %v short\", e.Short)\n}\n\ntype ErrDirty struct {\n\tVersion int\n}\n\nfunc (e ErrDirty) Error() string {\n\treturn fmt.Sprintf(\"Dirty database version %v. Fix and force version.\", e.Version)\n}\n\ntype Migrate struct {\n\tsourceName         string\n\tsourceDrv          source.Driver\n\tdatabaseDriverName string\n\tdatabaseDrv        database.Driver\n\n\t// Log accepts a Logger interface\n\tLog Logger\n\n\t// GracefulStop accepts `true` and will stop executing migrations\n\t// as soon as possible at a safe break point, so that the database\n\t// is not corrupted.\n\tGracefulStop chan bool\n\tisLockedMu   *sync.Mutex\n\n\tisGracefulStop bool\n\tisLocked       bool\n\n\t// PrefetchMigrations defaults to DefaultPrefetchMigrations,\n\t// but can be set per Migrate instance.\n\tPrefetchMigrations uint\n\n\t// LockTimeout defaults to DefaultLockTimeout,\n\t// but can be set per Migrate instance.\n\tLockTimeout time.Duration\n}\n\n// New returns a new Migrate instance from a source URL and a database URL.\n// The URL scheme is defined by each driver.\nfunc New(sourceURL, databaseURL string) (*Migrate, error) {\n\tm := newCommon()\n\n\tsourceName, err := iurl.SchemeFromURL(sourceURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse scheme from source URL: %w\", err)\n\t}\n\tm.sourceName = sourceName\n\n\tdatabaseDriverName, err := iurl.SchemeFromURL(databaseURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse scheme from database URL: %w\", err)\n\t}\n\tm.databaseDriverName = databaseDriverName\n\n\tsourceDrv, err := source.Open(sourceURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to open source, %q: %w\", sourceURL, err)\n\t}\n\tm.sourceDrv = sourceDrv\n\n\tdatabaseDrv, err := database.Open(databaseURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to open database: %w\", err)\n\t}\n\tm.databaseDrv = databaseDrv\n\n\treturn m, nil\n}\n\n// NewWithDatabaseInstance returns a new Migrate instance from a source URL\n// and an existing database instance. The source URL scheme is defined by each driver.\n// Use any string that can serve as an identifier during logging as databaseDriverName.\n// You are responsible for closing the underlying database client if necessary.\nfunc NewWithDatabaseInstance(sourceURL string, databaseDriverName string, databaseInstance database.Driver) (*Migrate, error) {\n\tm := newCommon()\n\n\tsourceName, err := iurl.SchemeFromURL(sourceURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tm.sourceName = sourceName\n\n\tm.databaseDriverName = databaseDriverName\n\n\tsourceDrv, err := source.Open(sourceURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to open source, %q: %w\", sourceURL, err)\n\t}\n\tm.sourceDrv = sourceDrv\n\n\tm.databaseDrv = databaseInstance\n\n\treturn m, nil\n}\n\n// NewWithSourceInstance returns a new Migrate instance from an existing source instance\n// and a database URL. The database URL scheme is defined by each driver.\n// Use any string that can serve as an identifier during logging as sourceName.\n// You are responsible for closing the underlying source client if necessary.\nfunc NewWithSourceInstance(sourceName string, sourceInstance source.Driver, databaseURL string) (*Migrate, error) {\n\tm := newCommon()\n\n\tdatabaseDriverName, err := iurl.SchemeFromURL(databaseURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse scheme from database URL: %w\", err)\n\t}\n\tm.databaseDriverName = databaseDriverName\n\n\tm.sourceName = sourceName\n\n\tdatabaseDrv, err := database.Open(databaseURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to open database: %w\", err)\n\t}\n\tm.databaseDrv = databaseDrv\n\n\tm.sourceDrv = sourceInstance\n\n\treturn m, nil\n}\n\n// NewWithInstance returns a new Migrate instance from an existing source and\n// database instance. Use any string that can serve as an identifier during logging\n// as sourceName and databaseDriverName. You are responsible for closing down\n// the underlying source and database client if necessary.\nfunc NewWithInstance(sourceName string, sourceInstance source.Driver, databaseDriverName string, databaseInstance database.Driver) (*Migrate, error) {\n\tm := newCommon()\n\n\tm.sourceName = sourceName\n\tm.databaseDriverName = databaseDriverName\n\n\tm.sourceDrv = sourceInstance\n\tm.databaseDrv = databaseInstance\n\n\treturn m, nil\n}\n\nfunc newCommon() *Migrate {\n\treturn &Migrate{\n\t\tGracefulStop:       make(chan bool, 1),\n\t\tPrefetchMigrations: DefaultPrefetchMigrations,\n\t\tLockTimeout:        DefaultLockTimeout,\n\t\tisLockedMu:         &sync.Mutex{},\n\t}\n}\n\n// Close closes the source and the database.\nfunc (m *Migrate) Close() (source error, database error) {\n\tdatabaseSrvClose := make(chan error)\n\tsourceSrvClose := make(chan error)\n\n\tm.logVerbosePrintf(\"Closing source and database\\n\")\n\n\tgo func() {\n\t\tdatabaseSrvClose <- m.databaseDrv.Close()\n\t}()\n\n\tgo func() {\n\t\tsourceSrvClose <- m.sourceDrv.Close()\n\t}()\n\n\treturn <-sourceSrvClose, <-databaseSrvClose\n}\n\n// Migrate looks at the currently active migration version,\n// then migrates either up or down to the specified version.\nfunc (m *Migrate) Migrate(version uint) error {\n\tif err := m.lock(); err != nil {\n\t\treturn err\n\t}\n\n\tcurVersion, dirty, err := m.databaseDrv.Version()\n\tif err != nil {\n\t\treturn m.unlockErr(err)\n\t}\n\n\tif dirty {\n\t\treturn m.unlockErr(ErrDirty{curVersion})\n\t}\n\n\tret := make(chan interface{}, m.PrefetchMigrations)\n\tgo m.read(curVersion, int(version), ret)\n\n\treturn m.unlockErr(m.runMigrations(ret))\n}\n\n// Steps looks at the currently active migration version.\n// It will migrate up if n > 0, and down if n < 0.\nfunc (m *Migrate) Steps(n int) error {\n\tif n == 0 {\n\t\treturn ErrNoChange\n\t}\n\n\tif err := m.lock(); err != nil {\n\t\treturn err\n\t}\n\n\tcurVersion, dirty, err := m.databaseDrv.Version()\n\tif err != nil {\n\t\treturn m.unlockErr(err)\n\t}\n\n\tif dirty {\n\t\treturn m.unlockErr(ErrDirty{curVersion})\n\t}\n\n\tret := make(chan interface{}, m.PrefetchMigrations)\n\n\tif n > 0 {\n\t\tgo m.readUp(curVersion, n, ret)\n\t} else {\n\t\tgo m.readDown(curVersion, -n, ret)\n\t}\n\n\treturn m.unlockErr(m.runMigrations(ret))\n}\n\n// Up looks at the currently active migration version\n// and will migrate all the way up (applying all up migrations).\nfunc (m *Migrate) Up() error {\n\tif err := m.lock(); err != nil {\n\t\treturn err\n\t}\n\n\tcurVersion, dirty, err := m.databaseDrv.Version()\n\tif err != nil {\n\t\treturn m.unlockErr(err)\n\t}\n\n\tif dirty {\n\t\treturn m.unlockErr(ErrDirty{curVersion})\n\t}\n\n\tret := make(chan interface{}, m.PrefetchMigrations)\n\n\tgo m.readUp(curVersion, -1, ret)\n\treturn m.unlockErr(m.runMigrations(ret))\n}\n\n// Down looks at the currently active migration version\n// and will migrate all the way down (applying all down migrations).\nfunc (m *Migrate) Down() error {\n\tif err := m.lock(); err != nil {\n\t\treturn err\n\t}\n\n\tcurVersion, dirty, err := m.databaseDrv.Version()\n\tif err != nil {\n\t\treturn m.unlockErr(err)\n\t}\n\n\tif dirty {\n\t\treturn m.unlockErr(ErrDirty{curVersion})\n\t}\n\n\tret := make(chan interface{}, m.PrefetchMigrations)\n\tgo m.readDown(curVersion, -1, ret)\n\treturn m.unlockErr(m.runMigrations(ret))\n}\n\n// Drop deletes everything in the database.\nfunc (m *Migrate) Drop() error {\n\tif err := m.lock(); err != nil {\n\t\treturn err\n\t}\n\tif err := m.databaseDrv.Drop(); err != nil {\n\t\treturn m.unlockErr(err)\n\t}\n\treturn m.unlock()\n}\n\n// Run runs any migration provided by you against the database.\n// It does not check any currently active version in database.\n// Usually you don't need this function at all. Use Migrate,\n// Steps, Up or Down instead.\nfunc (m *Migrate) Run(migration ...*Migration) error {\n\tif len(migration) == 0 {\n\t\treturn ErrNoChange\n\t}\n\n\tif err := m.lock(); err != nil {\n\t\treturn err\n\t}\n\n\tcurVersion, dirty, err := m.databaseDrv.Version()\n\tif err != nil {\n\t\treturn m.unlockErr(err)\n\t}\n\n\tif dirty {\n\t\treturn m.unlockErr(ErrDirty{curVersion})\n\t}\n\n\tret := make(chan interface{}, m.PrefetchMigrations)\n\n\tgo func() {\n\t\tdefer close(ret)\n\t\tfor _, migr := range migration {\n\t\t\tif m.PrefetchMigrations > 0 && migr.Body != nil {\n\t\t\t\tm.logVerbosePrintf(\"Start buffering %v\\n\", migr.LogString())\n\t\t\t} else {\n\t\t\t\tm.logVerbosePrintf(\"Scheduled %v\\n\", migr.LogString())\n\t\t\t}\n\n\t\t\tret <- migr\n\t\t\tgo func(migr *Migration) {\n\t\t\t\tif err := migr.Buffer(); err != nil {\n\t\t\t\t\tm.logErr(err)\n\t\t\t\t}\n\t\t\t}(migr)\n\t\t}\n\t}()\n\n\treturn m.unlockErr(m.runMigrations(ret))\n}\n\n// Force sets a migration version.\n// It does not check any currently active version in database.\n// It resets the dirty state to false.\nfunc (m *Migrate) Force(version int) error {\n\tif version < -1 {\n\t\treturn ErrInvalidVersion\n\t}\n\n\tif err := m.lock(); err != nil {\n\t\treturn err\n\t}\n\n\tif err := m.databaseDrv.SetVersion(version, false); err != nil {\n\t\treturn m.unlockErr(err)\n\t}\n\n\treturn m.unlock()\n}\n\n// Version returns the currently active migration version.\n// If no migration has been applied, yet, it will return ErrNilVersion.\nfunc (m *Migrate) Version() (version uint, dirty bool, err error) {\n\tv, d, err := m.databaseDrv.Version()\n\tif err != nil {\n\t\treturn 0, false, err\n\t}\n\n\tif v == database.NilVersion {\n\t\treturn 0, false, ErrNilVersion\n\t}\n\n\treturn suint(v), d, nil\n}\n\n// read reads either up or down migrations from source `from` to `to`.\n// Each migration is then written to the ret channel.\n// If an error occurs during reading, that error is written to the ret channel, too.\n// Once read is done reading it will close the ret channel.\nfunc (m *Migrate) read(from int, to int, ret chan<- interface{}) {\n\tdefer close(ret)\n\n\t// check if from version exists\n\tif from >= 0 {\n\t\tif err := m.versionExists(suint(from)); err != nil {\n\t\t\tret <- err\n\t\t\treturn\n\t\t}\n\t}\n\n\t// check if to version exists\n\tif to >= 0 {\n\t\tif err := m.versionExists(suint(to)); err != nil {\n\t\t\tret <- err\n\t\t\treturn\n\t\t}\n\t}\n\n\t// no change?\n\tif from == to {\n\t\tret <- ErrNoChange\n\t\treturn\n\t}\n\n\tif from < to {\n\t\t// it's going up\n\t\t// apply first migration if from is nil version\n\t\tif from == -1 {\n\t\t\tfirstVersion, err := m.sourceDrv.First()\n\t\t\tif err != nil {\n\t\t\t\tret <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tmigr, err := m.newMigration(firstVersion, int(firstVersion))\n\t\t\tif err != nil {\n\t\t\t\tret <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tret <- migr\n\t\t\tgo func() {\n\t\t\t\tif err := migr.Buffer(); err != nil {\n\t\t\t\t\tm.logErr(err)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tfrom = int(firstVersion)\n\t\t}\n\n\t\t// run until we reach target ...\n\t\tfor from < to {\n\t\t\tif m.stop() {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnext, err := m.sourceDrv.Next(suint(from))\n\t\t\tif err != nil {\n\t\t\t\tret <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tmigr, err := m.newMigration(next, int(next))\n\t\t\tif err != nil {\n\t\t\t\tret <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tret <- migr\n\t\t\tgo func() {\n\t\t\t\tif err := migr.Buffer(); err != nil {\n\t\t\t\t\tm.logErr(err)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tfrom = int(next)\n\t\t}\n\n\t} else {\n\t\t// it's going down\n\t\t// run until we reach target ...\n\t\tfor from > to && from >= 0 {\n\t\t\tif m.stop() {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tprev, err := m.sourceDrv.Prev(suint(from))\n\t\t\tif errors.Is(err, os.ErrNotExist) && to == -1 {\n\t\t\t\t// apply nil migration\n\t\t\t\tmigr, err := m.newMigration(suint(from), -1)\n\t\t\t\tif err != nil {\n\t\t\t\t\tret <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tret <- migr\n\t\t\t\tgo func() {\n\t\t\t\t\tif err := migr.Buffer(); err != nil {\n\t\t\t\t\t\tm.logErr(err)\n\t\t\t\t\t}\n\t\t\t\t}()\n\n\t\t\t\treturn\n\n\t\t\t} else if err != nil {\n\t\t\t\tret <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tmigr, err := m.newMigration(suint(from), int(prev))\n\t\t\tif err != nil {\n\t\t\t\tret <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tret <- migr\n\t\t\tgo func() {\n\t\t\t\tif err := migr.Buffer(); err != nil {\n\t\t\t\t\tm.logErr(err)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tfrom = int(prev)\n\t\t}\n\t}\n}\n\n// readUp reads up migrations from `from` limited by `limit`.\n// limit can be -1, implying no limit and reading until there are no more migrations.\n// Each migration is then written to the ret channel.\n// If an error occurs during reading, that error is written to the ret channel, too.\n// Once readUp is done reading it will close the ret channel.\nfunc (m *Migrate) readUp(from int, limit int, ret chan<- interface{}) {\n\tdefer close(ret)\n\n\t// check if from version exists\n\tif from >= 0 {\n\t\tif err := m.versionExists(suint(from)); err != nil {\n\t\t\tret <- err\n\t\t\treturn\n\t\t}\n\t}\n\n\tif limit == 0 {\n\t\tret <- ErrNoChange\n\t\treturn\n\t}\n\n\tcount := 0\n\tfor count < limit || limit == -1 {\n\t\tif m.stop() {\n\t\t\treturn\n\t\t}\n\n\t\t// apply first migration if from is nil version\n\t\tif from == -1 {\n\t\t\tfirstVersion, err := m.sourceDrv.First()\n\t\t\tif err != nil {\n\t\t\t\tret <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tmigr, err := m.newMigration(firstVersion, int(firstVersion))\n\t\t\tif err != nil {\n\t\t\t\tret <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tret <- migr\n\t\t\tgo func() {\n\t\t\t\tif err := migr.Buffer(); err != nil {\n\t\t\t\t\tm.logErr(err)\n\t\t\t\t}\n\t\t\t}()\n\t\t\tfrom = int(firstVersion)\n\t\t\tcount++\n\t\t\tcontinue\n\t\t}\n\n\t\t// apply next migration\n\t\tnext, err := m.sourceDrv.Next(suint(from))\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\t// no limit, but no migrations applied?\n\t\t\tif limit == -1 && count == 0 {\n\t\t\t\tret <- ErrNoChange\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// no limit, reached end\n\t\t\tif limit == -1 {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// reached end, and didn't apply any migrations\n\t\t\tif limit > 0 && count == 0 {\n\t\t\t\tret <- os.ErrNotExist\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// applied less migrations than limit?\n\t\t\tif count < limit {\n\t\t\t\tret <- ErrShortLimit{suint(limit - count)}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\tret <- err\n\t\t\treturn\n\t\t}\n\n\t\tmigr, err := m.newMigration(next, int(next))\n\t\tif err != nil {\n\t\t\tret <- err\n\t\t\treturn\n\t\t}\n\n\t\tret <- migr\n\t\tgo func() {\n\t\t\tif err := migr.Buffer(); err != nil {\n\t\t\t\tm.logErr(err)\n\t\t\t}\n\t\t}()\n\t\tfrom = int(next)\n\t\tcount++\n\t}\n}\n\n// readDown reads down migrations from `from` limited by `limit`.\n// limit can be -1, implying no limit and reading until there are no more migrations.\n// Each migration is then written to the ret channel.\n// If an error occurs during reading, that error is written to the ret channel, too.\n// Once readDown is done reading it will close the ret channel.\nfunc (m *Migrate) readDown(from int, limit int, ret chan<- interface{}) {\n\tdefer close(ret)\n\n\t// check if from version exists\n\tif from >= 0 {\n\t\tif err := m.versionExists(suint(from)); err != nil {\n\t\t\tret <- err\n\t\t\treturn\n\t\t}\n\t}\n\n\tif limit == 0 {\n\t\tret <- ErrNoChange\n\t\treturn\n\t}\n\n\t// no change if already at nil version\n\tif from == -1 && limit == -1 {\n\t\tret <- ErrNoChange\n\t\treturn\n\t}\n\n\t// can't go over limit if already at nil version\n\tif from == -1 && limit > 0 {\n\t\tret <- os.ErrNotExist\n\t\treturn\n\t}\n\n\tcount := 0\n\tfor count < limit || limit == -1 {\n\t\tif m.stop() {\n\t\t\treturn\n\t\t}\n\n\t\tprev, err := m.sourceDrv.Prev(suint(from))\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\t// no limit or haven't reached limit, apply \"first\" migration\n\t\t\tif limit == -1 || limit-count > 0 {\n\t\t\t\tfirstVersion, err := m.sourceDrv.First()\n\t\t\t\tif err != nil {\n\t\t\t\t\tret <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tmigr, err := m.newMigration(firstVersion, -1)\n\t\t\t\tif err != nil {\n\t\t\t\t\tret <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tret <- migr\n\t\t\t\tgo func() {\n\t\t\t\t\tif err := migr.Buffer(); err != nil {\n\t\t\t\t\t\tm.logErr(err)\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\tcount++\n\t\t\t}\n\n\t\t\tif count < limit {\n\t\t\t\tret <- ErrShortLimit{suint(limit - count)}\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif err != nil {\n\t\t\tret <- err\n\t\t\treturn\n\t\t}\n\n\t\tmigr, err := m.newMigration(suint(from), int(prev))\n\t\tif err != nil {\n\t\t\tret <- err\n\t\t\treturn\n\t\t}\n\n\t\tret <- migr\n\t\tgo func() {\n\t\t\tif err := migr.Buffer(); err != nil {\n\t\t\t\tm.logErr(err)\n\t\t\t}\n\t\t}()\n\t\tfrom = int(prev)\n\t\tcount++\n\t}\n}\n\n// runMigrations reads *Migration and error from a channel. Any other type\n// sent on this channel will result in a panic. Each migration is then\n// proxied to the database driver and run against the database.\n// Before running a newly received migration it will check if it's supposed\n// to stop execution because it might have received a stop signal on the\n// GracefulStop channel.\nfunc (m *Migrate) runMigrations(ret <-chan interface{}) error {\n\tfor r := range ret {\n\n\t\tif m.stop() {\n\t\t\treturn nil\n\t\t}\n\n\t\tswitch r := r.(type) {\n\t\tcase error:\n\t\t\treturn r\n\n\t\tcase *Migration:\n\t\t\tmigr := r\n\n\t\t\t// set version with dirty state\n\t\t\tif err := m.databaseDrv.SetVersion(migr.TargetVersion, true); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif migr.Body != nil {\n\t\t\t\tm.logVerbosePrintf(\"Read and execute %v\\n\", migr.LogString())\n\t\t\t\tif err := m.databaseDrv.Run(migr.BufferedBody); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// set clean state\n\t\t\tif err := m.databaseDrv.SetVersion(migr.TargetVersion, false); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tendTime := time.Now()\n\t\t\treadTime := migr.FinishedReading.Sub(migr.StartedBuffering)\n\t\t\trunTime := endTime.Sub(migr.FinishedReading)\n\n\t\t\t// log either verbose or normal\n\t\t\tif m.Log != nil {\n\t\t\t\tif m.Log.Verbose() {\n\t\t\t\t\tm.logPrintf(\"Finished %v (read %v, ran %v)\\n\", migr.LogString(), readTime, runTime)\n\t\t\t\t} else {\n\t\t\t\t\tm.logPrintf(\"%v (%v)\\n\", migr.LogString(), readTime+runTime)\n\t\t\t\t}\n\t\t\t}\n\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unknown type: %T with value: %+v\", r, r)\n\t\t}\n\t}\n\treturn nil\n}\n\n// versionExists checks the source if either the up or down migration for\n// the specified migration version exists.\nfunc (m *Migrate) versionExists(version uint) (result error) {\n\t// try up migration first\n\tup, _, err := m.sourceDrv.ReadUp(version)\n\tif err == nil {\n\t\tdefer func() {\n\t\t\tif errClose := up.Close(); errClose != nil {\n\t\t\t\tresult = errors.Join(result, errClose)\n\t\t\t}\n\t\t}()\n\t}\n\tif errors.Is(err, os.ErrExist) {\n\t\treturn nil\n\t} else if !errors.Is(err, os.ErrNotExist) {\n\t\treturn err\n\t}\n\n\t// then try down migration\n\tdown, _, err := m.sourceDrv.ReadDown(version)\n\tif err == nil {\n\t\tdefer func() {\n\t\t\tif errClose := down.Close(); errClose != nil {\n\t\t\t\tresult = errors.Join(result, errClose)\n\t\t\t}\n\t\t}()\n\t}\n\tif errors.Is(err, os.ErrExist) {\n\t\treturn nil\n\t} else if !errors.Is(err, os.ErrNotExist) {\n\t\treturn err\n\t}\n\n\terr = fmt.Errorf(\"no migration found for version %d: %w\", version, err)\n\tm.logErr(err)\n\treturn err\n}\n\n// stop returns true if no more migrations should be run against the database\n// because a stop signal was received on the GracefulStop channel.\n// Calls are cheap and this function is not blocking.\nfunc (m *Migrate) stop() bool {\n\tif m.isGracefulStop {\n\t\treturn true\n\t}\n\n\tselect {\n\tcase <-m.GracefulStop:\n\t\tm.isGracefulStop = true\n\t\treturn true\n\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// newMigration is a helper func that returns a *Migration for the\n// specified version and targetVersion.\nfunc (m *Migrate) newMigration(version uint, targetVersion int) (*Migration, error) {\n\tvar migr *Migration\n\n\tif targetVersion >= int(version) {\n\t\tr, identifier, err := m.sourceDrv.ReadUp(version)\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\t// create \"empty\" migration\n\t\t\tmigr, err = NewMigration(nil, \"\", version, targetVersion)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t} else if err != nil {\n\t\t\treturn nil, err\n\n\t\t} else {\n\t\t\t// create migration from up source\n\t\t\tmigr, err = NewMigration(r, identifier, version, targetVersion)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t} else {\n\t\tr, identifier, err := m.sourceDrv.ReadDown(version)\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\t// create \"empty\" migration\n\t\t\tmigr, err = NewMigration(nil, \"\", version, targetVersion)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t} else if err != nil {\n\t\t\treturn nil, err\n\n\t\t} else {\n\t\t\t// create migration from down source\n\t\t\tmigr, err = NewMigration(r, identifier, version, targetVersion)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\tif m.PrefetchMigrations > 0 && migr.Body != nil {\n\t\tm.logVerbosePrintf(\"Start buffering %v\\n\", migr.LogString())\n\t} else {\n\t\tm.logVerbosePrintf(\"Scheduled %v\\n\", migr.LogString())\n\t}\n\n\treturn migr, nil\n}\n\n// lock is a thread safe helper function to lock the database.\n// It should be called as late as possible when running migrations.\nfunc (m *Migrate) lock() error {\n\tm.isLockedMu.Lock()\n\tdefer m.isLockedMu.Unlock()\n\n\tif m.isLocked {\n\t\treturn ErrLocked\n\t}\n\n\t// create done channel, used in the timeout goroutine\n\tdone := make(chan bool, 1)\n\tdefer func() {\n\t\tdone <- true\n\t}()\n\n\t// use errchan to signal error back to this context\n\terrchan := make(chan error, 2)\n\n\t// start timeout goroutine\n\ttimeout := time.After(m.LockTimeout)\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\t\treturn\n\t\t\tcase <-timeout:\n\t\t\t\terrchan <- ErrLockTimeout\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\t// now try to acquire the lock\n\tgo func() {\n\t\tif err := m.databaseDrv.Lock(); err != nil {\n\t\t\terrchan <- err\n\t\t} else {\n\t\t\terrchan <- nil\n\t\t}\n\t}()\n\n\t// wait until we either receive ErrLockTimeout or error from Lock operation\n\terr := <-errchan\n\tif err == nil {\n\t\tm.isLocked = true\n\t}\n\treturn err\n}\n\n// unlock is a thread safe helper function to unlock the database.\n// It should be called as early as possible when no more migrations are\n// expected to be executed.\nfunc (m *Migrate) unlock() error {\n\tm.isLockedMu.Lock()\n\tdefer m.isLockedMu.Unlock()\n\n\tif err := m.databaseDrv.Unlock(); err != nil {\n\t\t// BUG: Can potentially create a deadlock. Add a timeout.\n\t\treturn err\n\t}\n\n\tm.isLocked = false\n\treturn nil\n}\n\n// unlockErr calls unlock and returns a combined error\n// if a prevErr is not nil.\nfunc (m *Migrate) unlockErr(prevErr error) error {\n\tif err := m.unlock(); err != nil {\n\t\tprevErr = errors.Join(prevErr, err)\n\t}\n\treturn prevErr\n}\n\n// logPrintf writes to m.Log if not nil\nfunc (m *Migrate) logPrintf(format string, v ...interface{}) {\n\tif m.Log != nil {\n\t\tm.Log.Printf(format, v...)\n\t}\n}\n\n// logVerbosePrintf writes to m.Log if not nil. Use for verbose logging output.\nfunc (m *Migrate) logVerbosePrintf(format string, v ...interface{}) {\n\tif m.Log != nil && m.Log.Verbose() {\n\t\tm.Log.Printf(format, v...)\n\t}\n}\n\n// logErr writes error to m.Log if not nil\nfunc (m *Migrate) logErr(err error) {\n\tif m.Log != nil {\n\t\tm.Log.Printf(\"error: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "migrate_test.go",
    "content": "package migrate\n\nimport (\n\t\"bytes\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\tdStub \"github.com/golang-migrate/migrate/v4/database/stub\"\n\t\"github.com/golang-migrate/migrate/v4/source\"\n\tsStub \"github.com/golang-migrate/migrate/v4/source/stub\"\n)\n\n// sourceStubMigrations hold the following migrations:\n// u = up migration, d = down migration, n = version\n//\n//\t|  1  |  -  |  3  |  4  |  5  |  -  |  7  |\n//\t| u d |  -  | u   | u d |   d |  -  | u d |\nvar sourceStubMigrations *source.Migrations\n\nconst (\n\tsrcDrvNameStub = \"stub\"\n\tdbDrvNameStub  = \"stub\"\n)\n\nfunc init() {\n\tsourceStubMigrations = source.NewMigrations()\n\tsourceStubMigrations.Append(&source.Migration{Version: 1, Direction: source.Up, Identifier: \"CREATE 1\"})\n\tsourceStubMigrations.Append(&source.Migration{Version: 1, Direction: source.Down, Identifier: \"DROP 1\"})\n\tsourceStubMigrations.Append(&source.Migration{Version: 3, Direction: source.Up, Identifier: \"CREATE 3\"})\n\tsourceStubMigrations.Append(&source.Migration{Version: 4, Direction: source.Up, Identifier: \"CREATE 4\"})\n\tsourceStubMigrations.Append(&source.Migration{Version: 4, Direction: source.Down, Identifier: \"DROP 4\"})\n\tsourceStubMigrations.Append(&source.Migration{Version: 5, Direction: source.Down, Identifier: \"DROP 5\"})\n\tsourceStubMigrations.Append(&source.Migration{Version: 7, Direction: source.Up, Identifier: \"CREATE 7\"})\n\tsourceStubMigrations.Append(&source.Migration{Version: 7, Direction: source.Down, Identifier: \"DROP 7\"})\n}\n\ntype DummyInstance struct{ Name string }\n\nfunc TestNew(t *testing.T) {\n\tm, err := New(\"stub://\", \"stub://\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif m.sourceName != srcDrvNameStub {\n\t\tt.Errorf(\"expected stub, got %v\", m.sourceName)\n\t}\n\tif m.sourceDrv == nil {\n\t\tt.Error(\"expected sourceDrv not to be nil\")\n\t}\n\n\tif m.databaseDriverName != dbDrvNameStub {\n\t\tt.Errorf(\"expected stub, got %v\", m.databaseDriverName)\n\t}\n\tif m.databaseDrv == nil {\n\t\tt.Error(\"expected databaseDrv not to be nil\")\n\t}\n}\n\nfunc ExampleNew() {\n\t// Read migrations from /home/mattes/migrations and connect to a local postgres database.\n\tm, err := New(\"file:///home/mattes/migrations\", \"postgres://mattes:secret@localhost:5432/database?sslmode=disable\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Migrate all the way up ...\n\tif err := m.Up(); err != nil && err != ErrNoChange {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc TestNewWithDatabaseInstance(t *testing.T) {\n\tdummyDb := &DummyInstance{\"database\"}\n\tdbInst, err := dStub.WithInstance(dummyDb, &dStub.Config{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tm, err := NewWithDatabaseInstance(\"stub://\", dbDrvNameStub, dbInst)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif m.sourceName != srcDrvNameStub {\n\t\tt.Errorf(\"expected stub, got %v\", m.sourceName)\n\t}\n\tif m.sourceDrv == nil {\n\t\tt.Error(\"expected sourceDrv not to be nil\")\n\t}\n\n\tif m.databaseDriverName != dbDrvNameStub {\n\t\tt.Errorf(\"expected stub, got %v\", m.databaseDriverName)\n\t}\n\tif m.databaseDrv == nil {\n\t\tt.Error(\"expected databaseDrv not to be nil\")\n\t}\n}\n\nfunc ExampleNewWithDatabaseInstance() {\n\t// Create and use an existing database instance.\n\tdb, err := sql.Open(\"postgres\", \"postgres://mattes:secret@localhost:5432/database?sslmode=disable\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}()\n\n\t// Create driver instance from db.\n\t// Check each driver if it supports the WithInstance function.\n\t// `import \"github.com/golang-migrate/migrate/v4/database/postgres\"`\n\tinstance, err := dStub.WithInstance(db, &dStub.Config{})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Read migrations from /home/mattes/migrations and connect to a local postgres database.\n\tm, err := NewWithDatabaseInstance(\"file:///home/mattes/migrations\", \"postgres\", instance)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Migrate all the way up ...\n\tif err := m.Up(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc TestNewWithSourceInstance(t *testing.T) {\n\tdummySource := &DummyInstance{\"source\"}\n\tsInst, err := sStub.WithInstance(dummySource, &sStub.Config{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tm, err := NewWithSourceInstance(srcDrvNameStub, sInst, \"stub://\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif m.sourceName != srcDrvNameStub {\n\t\tt.Errorf(\"expected stub, got %v\", m.sourceName)\n\t}\n\tif m.sourceDrv == nil {\n\t\tt.Error(\"expected sourceDrv not to be nil\")\n\t}\n\n\tif m.databaseDriverName != dbDrvNameStub {\n\t\tt.Errorf(\"expected stub, got %v\", m.databaseDriverName)\n\t}\n\tif m.databaseDrv == nil {\n\t\tt.Error(\"expected databaseDrv not to be nil\")\n\t}\n}\n\nfunc ExampleNewWithSourceInstance() {\n\tdi := &DummyInstance{\"think any client required for a source here\"}\n\n\t// Create driver instance from DummyInstance di.\n\t// Check each driver if it support the WithInstance function.\n\t// `import \"github.com/golang-migrate/migrate/v4/source/stub\"`\n\tinstance, err := sStub.WithInstance(di, &sStub.Config{})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Read migrations from Stub and connect to a local postgres database.\n\tm, err := NewWithSourceInstance(srcDrvNameStub, instance, \"postgres://mattes:secret@localhost:5432/database?sslmode=disable\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Migrate all the way up ...\n\tif err := m.Up(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc TestNewWithInstance(t *testing.T) {\n\tdummyDb := &DummyInstance{\"database\"}\n\tdbInst, err := dStub.WithInstance(dummyDb, &dStub.Config{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdummySource := &DummyInstance{\"source\"}\n\tsInst, err := sStub.WithInstance(dummySource, &sStub.Config{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tm, err := NewWithInstance(srcDrvNameStub, sInst, dbDrvNameStub, dbInst)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif m.sourceName != srcDrvNameStub {\n\t\tt.Errorf(\"expected stub, got %v\", m.sourceName)\n\t}\n\tif m.sourceDrv == nil {\n\t\tt.Error(\"expected sourceDrv not to be nil\")\n\t}\n\n\tif m.databaseDriverName != dbDrvNameStub {\n\t\tt.Errorf(\"expected stub, got %v\", m.databaseDriverName)\n\t}\n\tif m.databaseDrv == nil {\n\t\tt.Error(\"expected databaseDrv not to be nil\")\n\t}\n}\n\nfunc ExampleNewWithInstance() {\n\t// See NewWithDatabaseInstance and NewWithSourceInstance for an example.\n}\n\nfunc TestClose(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tsourceErr, databaseErr := m.Close()\n\tif sourceErr != nil {\n\t\tt.Error(sourceErr)\n\t}\n\tif databaseErr != nil {\n\t\tt.Error(databaseErr)\n\t}\n}\n\nfunc TestMigrate(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tm.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations\n\tdbDrv := m.databaseDrv.(*dStub.Stub)\n\n\ttt := []struct {\n\t\tversion       uint\n\t\texpectErr     error\n\t\texpectVersion uint\n\t\texpectSeq     migrationSequence\n\t}{\n\t\t// migrate all the way Up in single steps\n\t\t{\n\t\t\tversion:   0,\n\t\t\texpectErr: os.ErrNotExist,\n\t\t},\n\t\t{\n\t\t\tversion:       1,\n\t\t\texpectVersion: 1,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tversion:   2,\n\t\t\texpectErr: os.ErrNotExist,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tversion:       3,\n\t\t\texpectVersion: 3,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tversion:       4,\n\t\t\texpectVersion: 4,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tversion:       5,\n\t\t\texpectVersion: 5,\n\t\t\texpectSeq: migrationSequence{ // 5 has no up migration\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tversion:   6,\n\t\t\texpectErr: os.ErrNotExist,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tversion:       7,\n\t\t\texpectVersion: 7,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tversion:   8,\n\t\t\texpectErr: os.ErrNotExist,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t},\n\t\t},\n\n\t\t// migrate all the way Down in single steps\n\t\t{\n\t\t\tversion:   6,\n\t\t\texpectErr: os.ErrNotExist,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tversion:       5,\n\t\t\texpectVersion: 5,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tversion:       4,\n\t\t\texpectVersion: 4,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tversion:       3,\n\t\t\texpectVersion: 3,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tversion:   2,\n\t\t\texpectErr: os.ErrNotExist,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tversion:       1,\n\t\t\texpectVersion: 1,\n\t\t\texpectSeq: migrationSequence{ // 3 has no down migration\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tversion:   0,\n\t\t\texpectErr: os.ErrNotExist,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t},\n\t\t},\n\n\t\t// migrate all the way Up in one step\n\t\t{\n\t\t\tversion:       7,\n\t\t\texpectVersion: 7,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t},\n\t\t},\n\n\t\t// migrate all the way Down in one step\n\t\t{\n\t\t\tversion:       1,\n\t\t\texpectVersion: 1,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t},\n\t\t},\n\n\t\t// can't migrate the same version twice\n\t\t{\n\t\t\tversion:   1,\n\t\t\texpectErr: ErrNoChange,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t},\n\t\t},\n\t}\n\n\tfor i, v := range tt {\n\t\terr := m.Migrate(v.version)\n\t\tif (v.expectErr == os.ErrNotExist && !errors.Is(err, os.ErrNotExist)) ||\n\t\t\t(v.expectErr != os.ErrNotExist && err != v.expectErr) {\n\t\t\tt.Errorf(\"expected err %v, got %v, in %v\", v.expectErr, err, i)\n\n\t\t} else if err == nil {\n\t\t\tversion, _, err := m.Version()\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif version != v.expectVersion {\n\t\t\t\tt.Errorf(\"expected version %v, got %v, in %v\", v.expectVersion, version, i)\n\t\t\t}\n\t\t}\n\t\tequalDbSeq(t, i, v.expectSeq, dbDrv)\n\t}\n}\n\nfunc TestMigrateDirty(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tdbDrv := m.databaseDrv.(*dStub.Stub)\n\tif err := dbDrv.SetVersion(0, true); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr := m.Migrate(1)\n\tif _, ok := err.(ErrDirty); !ok {\n\t\tt.Fatalf(\"expected ErrDirty, got %v\", err)\n\t}\n}\n\nfunc TestSteps(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tm.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations\n\tdbDrv := m.databaseDrv.(*dStub.Stub)\n\n\ttt := []struct {\n\t\tsteps         int\n\t\texpectErr     error\n\t\texpectVersion int\n\t\texpectSeq     migrationSequence\n\t}{\n\t\t// step must be != 0\n\t\t{\n\t\t\tsteps:     0,\n\t\t\texpectErr: ErrNoChange,\n\t\t},\n\n\t\t// can't go Down if ErrNilVersion\n\t\t{\n\t\t\tsteps:     -1,\n\t\t\texpectErr: os.ErrNotExist,\n\t\t},\n\n\t\t// migrate all the way Up\n\t\t{\n\t\t\tsteps:         1,\n\t\t\texpectVersion: 1,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\")},\n\t\t},\n\t\t{\n\t\t\tsteps:         1,\n\t\t\texpectVersion: 3,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsteps:         1,\n\t\t\texpectVersion: 4,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsteps:         1,\n\t\t\texpectVersion: 5,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsteps:         1,\n\t\t\texpectVersion: 7,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsteps:     1,\n\t\t\texpectErr: os.ErrNotExist,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t},\n\t\t},\n\n\t\t// migrate all the way Down\n\t\t{\n\t\t\tsteps:         -1,\n\t\t\texpectVersion: 5,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsteps:         -1,\n\t\t\texpectVersion: 4,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsteps:         -1,\n\t\t\texpectVersion: 3,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsteps:         -1,\n\t\t\texpectVersion: 1,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsteps:         -1,\n\t\t\texpectVersion: -1,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t\tmr(\"DROP 1\"),\n\t\t\t},\n\t\t},\n\n\t\t// migrate Up in bigger step\n\t\t{\n\t\t\tsteps:         4,\n\t\t\texpectVersion: 5,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t\tmr(\"DROP 1\"),\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t},\n\t\t},\n\n\t\t// apply one migration, then reaches out of boundary\n\t\t{\n\t\t\tsteps:         2,\n\t\t\texpectErr:     ErrShortLimit{1},\n\t\t\texpectVersion: 7,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t\tmr(\"DROP 1\"),\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t},\n\t\t},\n\n\t\t// migrate Down in bigger step\n\t\t{\n\t\t\tsteps:         -4,\n\t\t\texpectVersion: 1,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t\tmr(\"DROP 1\"),\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t},\n\t\t},\n\n\t\t// apply one migration, then reaches out of boundary\n\t\t{\n\t\t\tsteps:         -2,\n\t\t\texpectErr:     ErrShortLimit{1},\n\t\t\texpectVersion: -1,\n\t\t\texpectSeq: migrationSequence{\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t\tmr(\"DROP 1\"),\n\t\t\t\tmr(\"CREATE 1\"),\n\t\t\t\tmr(\"CREATE 3\"),\n\t\t\t\tmr(\"CREATE 4\"),\n\t\t\t\tmr(\"CREATE 7\"),\n\t\t\t\tmr(\"DROP 7\"),\n\t\t\t\tmr(\"DROP 5\"),\n\t\t\t\tmr(\"DROP 4\"),\n\t\t\t\tmr(\"DROP 1\"),\n\t\t\t},\n\t\t},\n\t}\n\n\tfor i, v := range tt {\n\t\terr := m.Steps(v.steps)\n\t\tif (v.expectErr == os.ErrNotExist && !errors.Is(err, os.ErrNotExist)) ||\n\t\t\t(v.expectErr != os.ErrNotExist && err != v.expectErr) {\n\t\t\tt.Errorf(\"expected err %v, got %v, in %v\", v.expectErr, err, i)\n\n\t\t} else if err == nil {\n\t\t\tversion, _, err := m.Version()\n\t\t\tif err != ErrNilVersion && err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif v.expectVersion == -1 && err != ErrNilVersion {\n\t\t\t\tt.Errorf(\"expected ErrNilVersion, got %v, in %v\", version, i)\n\n\t\t\t} else if v.expectVersion >= 0 && version != uint(v.expectVersion) {\n\t\t\t\tt.Errorf(\"expected version %v, got %v, in %v\", v.expectVersion, version, i)\n\t\t\t}\n\t\t}\n\t\tequalDbSeq(t, i, v.expectSeq, dbDrv)\n\t}\n}\n\nfunc TestStepsDirty(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tdbDrv := m.databaseDrv.(*dStub.Stub)\n\tif err := dbDrv.SetVersion(0, true); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr := m.Steps(1)\n\tif _, ok := err.(ErrDirty); !ok {\n\t\tt.Fatalf(\"expected ErrDirty, got %v\", err)\n\t}\n}\n\nfunc TestUpAndDown(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tm.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations\n\tdbDrv := m.databaseDrv.(*dStub.Stub)\n\n\t// go Up first\n\tif err := m.Up(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\texpectedSequence := migrationSequence{\n\t\tmr(\"CREATE 1\"),\n\t\tmr(\"CREATE 3\"),\n\t\tmr(\"CREATE 4\"),\n\t\tmr(\"CREATE 7\"),\n\t}\n\tequalDbSeq(t, 0, expectedSequence, dbDrv)\n\n\t// go Down\n\tif err := m.Down(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\texpectedSequence = migrationSequence{\n\t\tmr(\"CREATE 1\"),\n\t\tmr(\"CREATE 3\"),\n\t\tmr(\"CREATE 4\"),\n\t\tmr(\"CREATE 7\"),\n\t\tmr(\"DROP 7\"),\n\t\tmr(\"DROP 5\"),\n\t\tmr(\"DROP 4\"),\n\t\tmr(\"DROP 1\"),\n\t}\n\tequalDbSeq(t, 1, expectedSequence, dbDrv)\n\n\t// go 1 Up and then all the way Up\n\tif err := m.Steps(1); err != nil {\n\t\tt.Fatal(err)\n\t}\n\texpectedSequence = migrationSequence{\n\t\tmr(\"CREATE 1\"),\n\t\tmr(\"CREATE 3\"),\n\t\tmr(\"CREATE 4\"),\n\t\tmr(\"CREATE 7\"),\n\t\tmr(\"DROP 7\"),\n\t\tmr(\"DROP 5\"),\n\t\tmr(\"DROP 4\"),\n\t\tmr(\"DROP 1\"),\n\t\tmr(\"CREATE 1\"),\n\t}\n\tequalDbSeq(t, 2, expectedSequence, dbDrv)\n\n\tif err := m.Up(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\texpectedSequence = migrationSequence{\n\t\tmr(\"CREATE 1\"),\n\t\tmr(\"CREATE 3\"),\n\t\tmr(\"CREATE 4\"),\n\t\tmr(\"CREATE 7\"),\n\t\tmr(\"DROP 7\"),\n\t\tmr(\"DROP 5\"),\n\t\tmr(\"DROP 4\"),\n\t\tmr(\"DROP 1\"),\n\t\tmr(\"CREATE 1\"),\n\t\tmr(\"CREATE 3\"),\n\t\tmr(\"CREATE 4\"),\n\t\tmr(\"CREATE 7\"),\n\t}\n\tequalDbSeq(t, 3, expectedSequence, dbDrv)\n\n\t// go 1 Down and then all the way Down\n\tif err := m.Steps(-1); err != nil {\n\t\tt.Fatal(err)\n\t}\n\texpectedSequence = migrationSequence{\n\t\tmr(\"CREATE 1\"),\n\t\tmr(\"CREATE 3\"),\n\t\tmr(\"CREATE 4\"),\n\t\tmr(\"CREATE 7\"),\n\t\tmr(\"DROP 7\"),\n\t\tmr(\"DROP 5\"),\n\t\tmr(\"DROP 4\"),\n\t\tmr(\"DROP 1\"),\n\t\tmr(\"CREATE 1\"),\n\t\tmr(\"CREATE 3\"),\n\t\tmr(\"CREATE 4\"),\n\t\tmr(\"CREATE 7\"),\n\t\tmr(\"DROP 7\"),\n\t}\n\tequalDbSeq(t, 1, expectedSequence, dbDrv)\n\n\tif err := m.Down(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\texpectedSequence = migrationSequence{\n\t\tmr(\"CREATE 1\"),\n\t\tmr(\"CREATE 3\"),\n\t\tmr(\"CREATE 4\"),\n\t\tmr(\"CREATE 7\"),\n\t\tmr(\"DROP 7\"),\n\t\tmr(\"DROP 5\"),\n\t\tmr(\"DROP 4\"),\n\t\tmr(\"DROP 1\"),\n\t\tmr(\"CREATE 1\"),\n\t\tmr(\"CREATE 3\"),\n\t\tmr(\"CREATE 4\"),\n\t\tmr(\"CREATE 7\"),\n\t\tmr(\"DROP 7\"),\n\t\tmr(\"DROP 5\"),\n\t\tmr(\"DROP 4\"),\n\t\tmr(\"DROP 1\"),\n\t}\n\tequalDbSeq(t, 1, expectedSequence, dbDrv)\n}\n\nfunc TestUpDirty(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tdbDrv := m.databaseDrv.(*dStub.Stub)\n\tif err := dbDrv.SetVersion(0, true); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr := m.Up()\n\tif _, ok := err.(ErrDirty); !ok {\n\t\tt.Fatalf(\"expected ErrDirty, got %v\", err)\n\t}\n}\n\nfunc TestDownDirty(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tdbDrv := m.databaseDrv.(*dStub.Stub)\n\tif err := dbDrv.SetVersion(0, true); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr := m.Down()\n\tif _, ok := err.(ErrDirty); !ok {\n\t\tt.Fatalf(\"expected ErrDirty, got %v\", err)\n\t}\n}\n\nfunc TestDrop(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tm.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations\n\tdbDrv := m.databaseDrv.(*dStub.Stub)\n\n\tif err := m.Drop(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif dbDrv.MigrationSequence[len(dbDrv.MigrationSequence)-1] != dStub.DROP {\n\t\tt.Fatalf(\"expected database to DROP, got sequence %v\", dbDrv.MigrationSequence)\n\t}\n}\n\nfunc TestVersion(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tdbDrv := m.databaseDrv.(*dStub.Stub)\n\n\t_, _, err := m.Version()\n\tif err != ErrNilVersion {\n\t\tt.Fatalf(\"expected ErrNilVersion, got %v\", err)\n\t}\n\n\tif err := dbDrv.Run(bytes.NewBufferString(\"1_up\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := dbDrv.SetVersion(1, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tv, _, err := m.Version()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif v != 1 {\n\t\tt.Fatalf(\"expected version 1, got %v\", v)\n\t}\n}\n\nfunc TestRun(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\n\tmx, err := NewMigration(nil, \"\", 1, 2)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := m.Run(mx); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tv, _, err := m.Version()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif v != 2 {\n\t\tt.Errorf(\"expected version 2, got %v\", v)\n\t}\n}\n\nfunc TestRunDirty(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tdbDrv := m.databaseDrv.(*dStub.Stub)\n\tif err := dbDrv.SetVersion(0, true); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tmigr, err := NewMigration(nil, \"\", 1, 2)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = m.Run(migr)\n\tif _, ok := err.(ErrDirty); !ok {\n\t\tt.Fatalf(\"expected ErrDirty, got %v\", err)\n\t}\n}\n\nfunc TestForce(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tm.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations\n\n\tif err := m.Force(7); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tv, dirty, err := m.Version()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif dirty {\n\t\tt.Errorf(\"expected dirty to be false\")\n\t}\n\tif v != 7 {\n\t\tt.Errorf(\"expected version to be 7\")\n\t}\n}\n\nfunc TestForceDirty(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tdbDrv := m.databaseDrv.(*dStub.Stub)\n\tif err := dbDrv.SetVersion(0, true); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := m.Force(1); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestRead(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tm.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations\n\n\ttt := []struct {\n\t\tfrom             int\n\t\tto               int\n\t\texpectErr        error\n\t\texpectMigrations migrationSequence\n\t}{\n\t\t{from: -1, to: -1, expectErr: ErrNoChange},\n\t\t{from: -1, to: 0, expectErr: os.ErrNotExist},\n\t\t{from: -1, to: 1, expectErr: nil, expectMigrations: newMigSeq(M(1))},\n\t\t{from: -1, to: 2, expectErr: os.ErrNotExist},\n\t\t{from: -1, to: 3, expectErr: nil, expectMigrations: newMigSeq(M(1), M(3))},\n\t\t{from: -1, to: 4, expectErr: nil, expectMigrations: newMigSeq(M(1), M(3), M(4))},\n\t\t{from: -1, to: 5, expectErr: nil, expectMigrations: newMigSeq(M(1), M(3), M(4), M(5))},\n\t\t{from: -1, to: 6, expectErr: os.ErrNotExist},\n\t\t{from: -1, to: 7, expectErr: nil, expectMigrations: newMigSeq(M(1), M(3), M(4), M(5), M(7))},\n\t\t{from: -1, to: 8, expectErr: os.ErrNotExist},\n\n\t\t{from: 0, to: -1, expectErr: os.ErrNotExist},\n\t\t{from: 0, to: 0, expectErr: os.ErrNotExist},\n\t\t{from: 0, to: 1, expectErr: os.ErrNotExist},\n\t\t{from: 0, to: 2, expectErr: os.ErrNotExist},\n\t\t{from: 0, to: 3, expectErr: os.ErrNotExist},\n\t\t{from: 0, to: 4, expectErr: os.ErrNotExist},\n\t\t{from: 0, to: 5, expectErr: os.ErrNotExist},\n\t\t{from: 0, to: 6, expectErr: os.ErrNotExist},\n\t\t{from: 0, to: 7, expectErr: os.ErrNotExist},\n\t\t{from: 0, to: 8, expectErr: os.ErrNotExist},\n\n\t\t{from: 1, to: -1, expectErr: nil, expectMigrations: newMigSeq(M(1, -1))},\n\t\t{from: 1, to: 0, expectErr: os.ErrNotExist},\n\t\t{from: 1, to: 1, expectErr: ErrNoChange},\n\t\t{from: 1, to: 2, expectErr: os.ErrNotExist},\n\t\t{from: 1, to: 3, expectErr: nil, expectMigrations: newMigSeq(M(3))},\n\t\t{from: 1, to: 4, expectErr: nil, expectMigrations: newMigSeq(M(3), M(4))},\n\t\t{from: 1, to: 5, expectErr: nil, expectMigrations: newMigSeq(M(3), M(4), M(5))},\n\t\t{from: 1, to: 6, expectErr: os.ErrNotExist},\n\t\t{from: 1, to: 7, expectErr: nil, expectMigrations: newMigSeq(M(3), M(4), M(5), M(7))},\n\t\t{from: 1, to: 8, expectErr: os.ErrNotExist},\n\n\t\t{from: 2, to: -1, expectErr: os.ErrNotExist},\n\t\t{from: 2, to: 0, expectErr: os.ErrNotExist},\n\t\t{from: 2, to: 1, expectErr: os.ErrNotExist},\n\t\t{from: 2, to: 2, expectErr: os.ErrNotExist},\n\t\t{from: 2, to: 3, expectErr: os.ErrNotExist},\n\t\t{from: 2, to: 4, expectErr: os.ErrNotExist},\n\t\t{from: 2, to: 5, expectErr: os.ErrNotExist},\n\t\t{from: 2, to: 6, expectErr: os.ErrNotExist},\n\t\t{from: 2, to: 7, expectErr: os.ErrNotExist},\n\t\t{from: 2, to: 8, expectErr: os.ErrNotExist},\n\n\t\t{from: 3, to: -1, expectErr: nil, expectMigrations: newMigSeq(M(3, 1), M(1, -1))},\n\t\t{from: 3, to: 0, expectErr: os.ErrNotExist},\n\t\t{from: 3, to: 1, expectErr: nil, expectMigrations: newMigSeq(M(3, 1))},\n\t\t{from: 3, to: 2, expectErr: os.ErrNotExist},\n\t\t{from: 3, to: 3, expectErr: ErrNoChange},\n\t\t{from: 3, to: 4, expectErr: nil, expectMigrations: newMigSeq(M(4))},\n\t\t{from: 3, to: 5, expectErr: nil, expectMigrations: newMigSeq(M(4), M(5))},\n\t\t{from: 3, to: 6, expectErr: os.ErrNotExist},\n\t\t{from: 3, to: 7, expectErr: nil, expectMigrations: newMigSeq(M(4), M(5), M(7))},\n\t\t{from: 3, to: 8, expectErr: os.ErrNotExist},\n\n\t\t{from: 4, to: -1, expectErr: nil, expectMigrations: newMigSeq(M(4, 3), M(3, 1), M(1, -1))},\n\t\t{from: 4, to: 0, expectErr: os.ErrNotExist},\n\t\t{from: 4, to: 1, expectErr: nil, expectMigrations: newMigSeq(M(4, 3), M(3, 1))},\n\t\t{from: 4, to: 2, expectErr: os.ErrNotExist},\n\t\t{from: 4, to: 3, expectErr: nil, expectMigrations: newMigSeq(M(4, 3))},\n\t\t{from: 4, to: 4, expectErr: ErrNoChange},\n\t\t{from: 4, to: 5, expectErr: nil, expectMigrations: newMigSeq(M(5))},\n\t\t{from: 4, to: 6, expectErr: os.ErrNotExist},\n\t\t{from: 4, to: 7, expectErr: nil, expectMigrations: newMigSeq(M(5), M(7))},\n\t\t{from: 4, to: 8, expectErr: os.ErrNotExist},\n\n\t\t{from: 5, to: -1, expectErr: nil, expectMigrations: newMigSeq(M(5, 4), M(4, 3), M(3, 1), M(1, -1))},\n\t\t{from: 5, to: 0, expectErr: os.ErrNotExist},\n\t\t{from: 5, to: 1, expectErr: nil, expectMigrations: newMigSeq(M(5, 4), M(4, 3), M(3, 1))},\n\t\t{from: 5, to: 2, expectErr: os.ErrNotExist},\n\t\t{from: 5, to: 3, expectErr: nil, expectMigrations: newMigSeq(M(5, 4), M(4, 3))},\n\t\t{from: 5, to: 4, expectErr: nil, expectMigrations: newMigSeq(M(5, 4))},\n\t\t{from: 5, to: 5, expectErr: ErrNoChange},\n\t\t{from: 5, to: 6, expectErr: os.ErrNotExist},\n\t\t{from: 5, to: 7, expectErr: nil, expectMigrations: newMigSeq(M(7))},\n\t\t{from: 5, to: 8, expectErr: os.ErrNotExist},\n\n\t\t{from: 6, to: -1, expectErr: os.ErrNotExist},\n\t\t{from: 6, to: 0, expectErr: os.ErrNotExist},\n\t\t{from: 6, to: 1, expectErr: os.ErrNotExist},\n\t\t{from: 6, to: 2, expectErr: os.ErrNotExist},\n\t\t{from: 6, to: 3, expectErr: os.ErrNotExist},\n\t\t{from: 6, to: 4, expectErr: os.ErrNotExist},\n\t\t{from: 6, to: 5, expectErr: os.ErrNotExist},\n\t\t{from: 6, to: 6, expectErr: os.ErrNotExist},\n\t\t{from: 6, to: 7, expectErr: os.ErrNotExist},\n\t\t{from: 6, to: 8, expectErr: os.ErrNotExist},\n\n\t\t{from: 7, to: -1, expectErr: nil, expectMigrations: newMigSeq(M(7, 5), M(5, 4), M(4, 3), M(3, 1), M(1, -1))},\n\t\t{from: 7, to: 0, expectErr: os.ErrNotExist},\n\t\t{from: 7, to: 1, expectErr: nil, expectMigrations: newMigSeq(M(7, 5), M(5, 4), M(4, 3), M(3, 1))},\n\t\t{from: 7, to: 2, expectErr: os.ErrNotExist},\n\t\t{from: 7, to: 3, expectErr: nil, expectMigrations: newMigSeq(M(7, 5), M(5, 4), M(4, 3))},\n\t\t{from: 7, to: 4, expectErr: nil, expectMigrations: newMigSeq(M(7, 5), M(5, 4))},\n\t\t{from: 7, to: 5, expectErr: nil, expectMigrations: newMigSeq(M(7, 5))},\n\t\t{from: 7, to: 6, expectErr: os.ErrNotExist},\n\t\t{from: 7, to: 7, expectErr: ErrNoChange},\n\t\t{from: 7, to: 8, expectErr: os.ErrNotExist},\n\n\t\t{from: 8, to: -1, expectErr: os.ErrNotExist},\n\t\t{from: 8, to: 0, expectErr: os.ErrNotExist},\n\t\t{from: 8, to: 1, expectErr: os.ErrNotExist},\n\t\t{from: 8, to: 2, expectErr: os.ErrNotExist},\n\t\t{from: 8, to: 3, expectErr: os.ErrNotExist},\n\t\t{from: 8, to: 4, expectErr: os.ErrNotExist},\n\t\t{from: 8, to: 5, expectErr: os.ErrNotExist},\n\t\t{from: 8, to: 6, expectErr: os.ErrNotExist},\n\t\t{from: 8, to: 7, expectErr: os.ErrNotExist},\n\t\t{from: 8, to: 8, expectErr: os.ErrNotExist},\n\t}\n\n\tfor i, v := range tt {\n\t\tret := make(chan interface{})\n\t\tgo m.read(v.from, v.to, ret)\n\t\tmigrations, err := migrationsFromChannel(ret)\n\n\t\tif (v.expectErr == os.ErrNotExist && !errors.Is(err, os.ErrNotExist)) ||\n\t\t\t(v.expectErr != os.ErrNotExist && v.expectErr != err) {\n\t\t\tt.Errorf(\"expected %v, got %v, in %v\", v.expectErr, err, i)\n\t\t\tt.Logf(\"%v, in %v\", migrations, i)\n\t\t}\n\t\tif len(v.expectMigrations) > 0 {\n\t\t\tequalMigSeq(t, i, v.expectMigrations, migrations)\n\t\t}\n\t}\n}\n\nfunc TestReadUp(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tm.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations\n\n\ttt := []struct {\n\t\tfrom             int\n\t\tlimit            int // -1 means no limit\n\t\texpectErr        error\n\t\texpectMigrations migrationSequence\n\t}{\n\t\t{from: -1, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(1), M(3), M(4), M(5), M(7))},\n\t\t{from: -1, limit: 0, expectErr: ErrNoChange},\n\t\t{from: -1, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(1))},\n\t\t{from: -1, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(1), M(3))},\n\n\t\t{from: 0, limit: -1, expectErr: os.ErrNotExist},\n\t\t{from: 0, limit: 0, expectErr: os.ErrNotExist},\n\t\t{from: 0, limit: 1, expectErr: os.ErrNotExist},\n\t\t{from: 0, limit: 2, expectErr: os.ErrNotExist},\n\n\t\t{from: 1, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(3), M(4), M(5), M(7))},\n\t\t{from: 1, limit: 0, expectErr: ErrNoChange},\n\t\t{from: 1, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(3))},\n\t\t{from: 1, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(3), M(4))},\n\n\t\t{from: 2, limit: -1, expectErr: os.ErrNotExist},\n\t\t{from: 2, limit: 0, expectErr: os.ErrNotExist},\n\t\t{from: 2, limit: 1, expectErr: os.ErrNotExist},\n\t\t{from: 2, limit: 2, expectErr: os.ErrNotExist},\n\n\t\t{from: 3, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(4), M(5), M(7))},\n\t\t{from: 3, limit: 0, expectErr: ErrNoChange},\n\t\t{from: 3, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(4))},\n\t\t{from: 3, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(4), M(5))},\n\n\t\t{from: 4, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(5), M(7))},\n\t\t{from: 4, limit: 0, expectErr: ErrNoChange},\n\t\t{from: 4, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(5))},\n\t\t{from: 4, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(5), M(7))},\n\n\t\t{from: 5, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(7))},\n\t\t{from: 5, limit: 0, expectErr: ErrNoChange},\n\t\t{from: 5, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(7))},\n\t\t{from: 5, limit: 2, expectErr: ErrShortLimit{1}, expectMigrations: newMigSeq(M(7))},\n\n\t\t{from: 6, limit: -1, expectErr: os.ErrNotExist},\n\t\t{from: 6, limit: 0, expectErr: os.ErrNotExist},\n\t\t{from: 6, limit: 1, expectErr: os.ErrNotExist},\n\t\t{from: 6, limit: 2, expectErr: os.ErrNotExist},\n\n\t\t{from: 7, limit: -1, expectErr: ErrNoChange},\n\t\t{from: 7, limit: 0, expectErr: ErrNoChange},\n\t\t{from: 7, limit: 1, expectErr: os.ErrNotExist},\n\t\t{from: 7, limit: 2, expectErr: os.ErrNotExist},\n\n\t\t{from: 8, limit: -1, expectErr: os.ErrNotExist},\n\t\t{from: 8, limit: 0, expectErr: os.ErrNotExist},\n\t\t{from: 8, limit: 1, expectErr: os.ErrNotExist},\n\t\t{from: 8, limit: 2, expectErr: os.ErrNotExist},\n\t}\n\n\tfor i, v := range tt {\n\t\tret := make(chan interface{})\n\t\tgo m.readUp(v.from, v.limit, ret)\n\t\tmigrations, err := migrationsFromChannel(ret)\n\n\t\tif (v.expectErr == os.ErrNotExist && !errors.Is(err, os.ErrNotExist)) ||\n\t\t\t(v.expectErr != os.ErrNotExist && v.expectErr != err) {\n\t\t\tt.Errorf(\"expected %v, got %v, in %v\", v.expectErr, err, i)\n\t\t\tt.Logf(\"%v, in %v\", migrations, i)\n\t\t}\n\t\tif len(v.expectMigrations) > 0 {\n\t\t\tequalMigSeq(t, i, v.expectMigrations, migrations)\n\t\t}\n\t}\n}\n\nfunc TestReadDown(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tm.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations\n\n\ttt := []struct {\n\t\tfrom             int\n\t\tlimit            int // -1 means no limit\n\t\texpectErr        error\n\t\texpectMigrations migrationSequence\n\t}{\n\t\t{from: -1, limit: -1, expectErr: ErrNoChange},\n\t\t{from: -1, limit: 0, expectErr: ErrNoChange},\n\t\t{from: -1, limit: 1, expectErr: os.ErrNotExist},\n\t\t{from: -1, limit: 2, expectErr: os.ErrNotExist},\n\n\t\t{from: 0, limit: -1, expectErr: os.ErrNotExist},\n\t\t{from: 0, limit: 0, expectErr: os.ErrNotExist},\n\t\t{from: 0, limit: 1, expectErr: os.ErrNotExist},\n\t\t{from: 0, limit: 2, expectErr: os.ErrNotExist},\n\n\t\t{from: 1, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(1, -1))},\n\t\t{from: 1, limit: 0, expectErr: ErrNoChange},\n\t\t{from: 1, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(1, -1))},\n\t\t{from: 1, limit: 2, expectErr: ErrShortLimit{1}, expectMigrations: newMigSeq(M(1, -1))},\n\n\t\t{from: 2, limit: -1, expectErr: os.ErrNotExist},\n\t\t{from: 2, limit: 0, expectErr: os.ErrNotExist},\n\t\t{from: 2, limit: 1, expectErr: os.ErrNotExist},\n\t\t{from: 2, limit: 2, expectErr: os.ErrNotExist},\n\n\t\t{from: 3, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(3, 1), M(1, -1))},\n\t\t{from: 3, limit: 0, expectErr: ErrNoChange},\n\t\t{from: 3, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(3, 1))},\n\t\t{from: 3, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(3, 1), M(1, -1))},\n\n\t\t{from: 4, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(4, 3), M(3, 1), M(1, -1))},\n\t\t{from: 4, limit: 0, expectErr: ErrNoChange},\n\t\t{from: 4, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(4, 3))},\n\t\t{from: 4, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(4, 3), M(3, 1))},\n\n\t\t{from: 5, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(5, 4), M(4, 3), M(3, 1), M(1, -1))},\n\t\t{from: 5, limit: 0, expectErr: ErrNoChange},\n\t\t{from: 5, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(5, 4))},\n\t\t{from: 5, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(5, 4), M(4, 3))},\n\n\t\t{from: 6, limit: -1, expectErr: os.ErrNotExist},\n\t\t{from: 6, limit: 0, expectErr: os.ErrNotExist},\n\t\t{from: 6, limit: 1, expectErr: os.ErrNotExist},\n\t\t{from: 6, limit: 2, expectErr: os.ErrNotExist},\n\n\t\t{from: 7, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(7, 5), M(5, 4), M(4, 3), M(3, 1), M(1, -1))},\n\t\t{from: 7, limit: 0, expectErr: ErrNoChange},\n\t\t{from: 7, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(7, 5))},\n\t\t{from: 7, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(7, 5), M(5, 4))},\n\n\t\t{from: 8, limit: -1, expectErr: os.ErrNotExist},\n\t\t{from: 8, limit: 0, expectErr: os.ErrNotExist},\n\t\t{from: 8, limit: 1, expectErr: os.ErrNotExist},\n\t\t{from: 8, limit: 2, expectErr: os.ErrNotExist},\n\t}\n\n\tfor i, v := range tt {\n\t\tret := make(chan interface{})\n\t\tgo m.readDown(v.from, v.limit, ret)\n\t\tmigrations, err := migrationsFromChannel(ret)\n\n\t\tif (v.expectErr == os.ErrNotExist && !errors.Is(err, os.ErrNotExist)) ||\n\t\t\t(v.expectErr != os.ErrNotExist && v.expectErr != err) {\n\t\t\tt.Errorf(\"expected %v, got %v, in %v\", v.expectErr, err, i)\n\t\t\tt.Logf(\"%v, in %v\", migrations, i)\n\t\t}\n\t\tif len(v.expectMigrations) > 0 {\n\t\t\tequalMigSeq(t, i, v.expectMigrations, migrations)\n\t\t}\n\t}\n}\n\nfunc TestLock(t *testing.T) {\n\tm, _ := New(\"stub://\", \"stub://\")\n\tif err := m.lock(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := m.lock(); err == nil {\n\t\tt.Fatal(\"should be locked already\")\n\t}\n}\n\nfunc migrationsFromChannel(ret chan interface{}) ([]*Migration, error) {\n\tslice := make([]*Migration, 0)\n\tfor r := range ret {\n\t\tswitch t := r.(type) {\n\t\tcase error:\n\t\t\treturn slice, t\n\n\t\tcase *Migration:\n\t\t\tslice = append(slice, t)\n\t\t}\n\t}\n\treturn slice, nil\n}\n\ntype migrationSequence []*Migration\n\nfunc newMigSeq(migr ...*Migration) migrationSequence {\n\treturn migr\n}\n\nfunc (m *migrationSequence) bodySequence() []string {\n\tr := make([]string, 0)\n\tfor _, v := range *m {\n\t\tif v.Body != nil {\n\t\t\tbody, err := io.ReadAll(v.Body)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err) // that should never happen\n\t\t\t}\n\n\t\t\t// reset body reader\n\t\t\t// TODO: is there a better/nicer way?\n\t\t\tv.Body = io.NopCloser(bytes.NewReader(body))\n\n\t\t\tr = append(r, string(body[:]))\n\t\t} else {\n\t\t\tr = append(r, \"<empty>\")\n\t\t}\n\t}\n\treturn r\n}\n\n// M is a convenience func to create a new *Migration\nfunc M(version uint, targetVersion ...int) *Migration {\n\tif len(targetVersion) > 1 {\n\t\tpanic(\"only one targetVersion allowed\")\n\t}\n\tts := int(version)\n\tif len(targetVersion) == 1 {\n\t\tts = targetVersion[0]\n\t}\n\n\tm, _ := New(\"stub://\", \"stub://\")\n\tm.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations\n\tmigr, err := m.newMigration(version, ts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn migr\n}\n\n// mr is a convenience func to create a new *Migration from the raw database query\nfunc mr(value string) *Migration {\n\treturn &Migration{\n\t\tBody: io.NopCloser(strings.NewReader(value)),\n\t}\n}\n\nfunc equalMigSeq(t *testing.T, i int, expected, got migrationSequence) {\n\tif len(expected) != len(got) {\n\t\tt.Errorf(\"expected migrations %v, got %v, in %v\", expected, got, i)\n\n\t} else {\n\t\tfor ii := 0; ii < len(expected); ii++ {\n\t\t\tif expected[ii].Version != got[ii].Version {\n\t\t\t\tt.Errorf(\"expected version %v, got %v, in %v\", expected[ii].Version, got[ii].Version, i)\n\t\t\t}\n\n\t\t\tif expected[ii].TargetVersion != got[ii].TargetVersion {\n\t\t\t\tt.Errorf(\"expected targetVersion %v, got %v, in %v\", expected[ii].TargetVersion, got[ii].TargetVersion, i)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc equalDbSeq(t *testing.T, i int, expected migrationSequence, got *dStub.Stub) {\n\tbs := expected.bodySequence()\n\tif !got.EqualSequence(bs) {\n\t\tt.Fatalf(\"\\nexpected sequence %v,\\ngot               %v, in %v\", bs, got.MigrationSequence, i)\n\t}\n}\n"
  },
  {
    "path": "migration.go",
    "content": "package migrate\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n)\n\n// DefaultBufferSize sets the in memory buffer size (in Bytes) for every\n// pre-read migration (see DefaultPrefetchMigrations).\nvar DefaultBufferSize = uint(100000)\n\n// Migration holds information about a migration.\n// It is initially created from data coming from the source and then\n// used when run against the database.\ntype Migration struct {\n\t// Identifier can be any string to help identifying\n\t// the migration in the source.\n\tIdentifier string\n\n\t// Version is the version of this migration.\n\tVersion uint\n\n\t// TargetVersion is the migration version after this migration\n\t// has been applied to the database.\n\t// Can be -1, implying that this is a NilVersion.\n\tTargetVersion int\n\n\t// Body holds an io.ReadCloser to the source.\n\tBody io.ReadCloser\n\n\t// BufferedBody holds an buffered io.Reader to the underlying Body.\n\tBufferedBody io.Reader\n\n\t// BufferSize defaults to DefaultBufferSize\n\tBufferSize uint\n\n\t// bufferWriter holds an io.WriteCloser and pipes to BufferBody.\n\t// It's an *Closer for flow control.\n\tbufferWriter io.WriteCloser\n\n\t// Scheduled is the time when the migration was scheduled/ queued.\n\tScheduled time.Time\n\n\t// StartedBuffering is the time when buffering of the migration source started.\n\tStartedBuffering time.Time\n\n\t// FinishedBuffering is the time when buffering of the migration source finished.\n\tFinishedBuffering time.Time\n\n\t// FinishedReading is the time when the migration source is fully read.\n\tFinishedReading time.Time\n\n\t// BytesRead holds the number of Bytes read from the migration source.\n\tBytesRead int64\n}\n\n// NewMigration returns a new Migration and sets the body, identifier,\n// version and targetVersion. Body can be nil, which turns this migration\n// into a \"NilMigration\". If no identifier is provided, it will default to \"<empty>\".\n// targetVersion can be -1, implying it is a NilVersion.\n//\n// What is a NilMigration?\n// Usually each migration version coming from source is expected to have an\n// Up and Down migration. This is not a hard requirement though, leading to\n// a situation where only the Up or Down migration is present. So let's say\n// the user wants to migrate up to a version that doesn't have the actual Up\n// migration, in that case we still want to apply the version, but with an empty\n// body. We are calling that a NilMigration, a migration with an empty body.\n//\n// What is a NilVersion?\n// NilVersion is a const(-1). When running down migrations and we are at the\n// last down migration, there is no next down migration, the targetVersion should\n// be nil. Nil in this case is represented by -1 (because type int).\nfunc NewMigration(body io.ReadCloser, identifier string,\n\tversion uint, targetVersion int) (*Migration, error) {\n\ttnow := time.Now()\n\tm := &Migration{\n\t\tIdentifier:    identifier,\n\t\tVersion:       version,\n\t\tTargetVersion: targetVersion,\n\t\tScheduled:     tnow,\n\t}\n\n\tif body == nil {\n\t\tif len(identifier) == 0 {\n\t\t\tm.Identifier = \"<empty>\"\n\t\t}\n\n\t\tm.StartedBuffering = tnow\n\t\tm.FinishedBuffering = tnow\n\t\tm.FinishedReading = tnow\n\t\treturn m, nil\n\t}\n\n\tbr, bw := io.Pipe()\n\tm.Body = body // want to simulate low latency? newSlowReader(body)\n\tm.BufferSize = DefaultBufferSize\n\tm.BufferedBody = br\n\tm.bufferWriter = bw\n\treturn m, nil\n}\n\n// String implements string.Stringer and is used in tests.\nfunc (m *Migration) String() string {\n\treturn fmt.Sprintf(\"%v [%v=>%v]\", m.Identifier, m.Version, m.TargetVersion)\n}\n\n// LogString returns a string describing this migration to humans.\nfunc (m *Migration) LogString() string {\n\tdirectionStr := \"u\"\n\tif m.TargetVersion < int(m.Version) {\n\t\tdirectionStr = \"d\"\n\t}\n\treturn fmt.Sprintf(\"%v/%v %v\", m.Version, directionStr, m.Identifier)\n}\n\n// Buffer buffers Body up to BufferSize.\n// Calling this function blocks. Call with goroutine.\nfunc (m *Migration) Buffer() (berr error) {\n\tif m.Body == nil {\n\t\treturn nil\n\t}\n\n\tm.StartedBuffering = time.Now()\n\n\tb := bufio.NewReaderSize(m.Body, int(m.BufferSize))\n\n\t// defer closing buffer writer and body.\n\tdefer func() {\n\t\t// close bufferWriter so Buffer knows that there is no\n\t\t// more data coming.\n\t\tif err := m.bufferWriter.Close(); err != nil {\n\t\t\tberr = errors.Join(berr, err)\n\t\t}\n\n\t\t// it's safe to close the Body too.\n\t\tif err := m.Body.Close(); err != nil {\n\t\t\tberr = errors.Join(berr, err)\n\t\t}\n\n\t}()\n\n\t// start reading from body, peek won't move the read pointer though\n\t// poor man's solution?\n\tif _, err := b.Peek(int(m.BufferSize)); err != nil && err != io.EOF {\n\t\treturn err\n\t}\n\n\tm.FinishedBuffering = time.Now()\n\n\t// write to bufferWriter, this will block until\n\t// something starts reading from m.Buffer\n\tn, err := b.WriteTo(m.bufferWriter)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tm.FinishedReading = time.Now()\n\tm.BytesRead = n\n\n\treturn nil\n}\n"
  },
  {
    "path": "migration_test.go",
    "content": "package migrate\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"strings\"\n)\n\nfunc ExampleNewMigration() {\n\t// Create a dummy migration body, this is coming from the source usually.\n\tbody := io.NopCloser(strings.NewReader(\"dumy migration that creates users table\"))\n\n\t// Create a new Migration that represents version 1486686016.\n\t// Once this migration has been applied to the database, the new\n\t// migration version will be 1486689359.\n\tmigr, err := NewMigration(body, \"create_users_table\", 1486686016, 1486689359)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tfmt.Print(migr.LogString())\n\t// Output:\n\t// 1486686016/u create_users_table\n}\n\nfunc ExampleNewMigration_nilMigration() {\n\t// Create a new Migration that represents a NilMigration.\n\t// Once this migration has been applied to the database, the new\n\t// migration version will be 1486689359.\n\tmigr, err := NewMigration(nil, \"\", 1486686016, 1486689359)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tfmt.Print(migr.LogString())\n\t// Output:\n\t// 1486686016/u <empty>\n}\n\nfunc ExampleNewMigration_nilVersion() {\n\t// Create a dummy migration body, this is coming from the source usually.\n\tbody := io.NopCloser(strings.NewReader(\"dumy migration that deletes users table\"))\n\n\t// Create a new Migration that represents version 1486686016.\n\t// This is the last available down migration, so the migration version\n\t// will be -1, meaning NilVersion once this migration ran.\n\tmigr, err := NewMigration(body, \"drop_users_table\", 1486686016, -1)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tfmt.Print(migr.LogString())\n\t// Output:\n\t// 1486686016/d drop_users_table\n}\n"
  },
  {
    "path": "source/aws_s3/README.md",
    "content": "# aws_s3\n\n`s3://<bucket>/<prefix>`\n"
  },
  {
    "path": "source/aws_s3/s3.go",
    "content": "package awss3\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/aws/aws-sdk-go/service/s3\"\n\t\"github.com/aws/aws-sdk-go/service/s3/s3iface\"\n\t\"github.com/golang-migrate/migrate/v4/source\"\n)\n\nfunc init() {\n\tsource.Register(\"s3\", &s3Driver{})\n}\n\ntype s3Driver struct {\n\ts3client   s3iface.S3API\n\tconfig     *Config\n\tmigrations *source.Migrations\n}\n\ntype Config struct {\n\tBucket string\n\tPrefix string\n}\n\nfunc (s *s3Driver) Open(folder string) (source.Driver, error) {\n\tconfig, err := parseURI(folder)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsess, err := session.NewSession()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn WithInstance(s3.New(sess), config)\n}\n\nfunc WithInstance(s3client s3iface.S3API, config *Config) (source.Driver, error) {\n\tdriver := &s3Driver{\n\t\tconfig:     config,\n\t\ts3client:   s3client,\n\t\tmigrations: source.NewMigrations(),\n\t}\n\n\tif err := driver.loadMigrations(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn driver, nil\n}\n\nfunc parseURI(uri string) (*Config, error) {\n\tu, err := url.Parse(uri)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprefix := strings.Trim(u.Path, \"/\")\n\tif prefix != \"\" {\n\t\tprefix += \"/\"\n\t}\n\n\treturn &Config{\n\t\tBucket: u.Host,\n\t\tPrefix: prefix,\n\t}, nil\n}\n\nfunc (s *s3Driver) loadMigrations() error {\n\toutput, err := s.s3client.ListObjects(&s3.ListObjectsInput{\n\t\tBucket:    aws.String(s.config.Bucket),\n\t\tPrefix:    aws.String(s.config.Prefix),\n\t\tDelimiter: aws.String(\"/\"),\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, object := range output.Contents {\n\t\t_, fileName := path.Split(aws.StringValue(object.Key))\n\t\tm, err := source.DefaultParse(fileName)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif !s.migrations.Append(m) {\n\t\t\treturn fmt.Errorf(\"unable to parse file %v\", aws.StringValue(object.Key))\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *s3Driver) Close() error {\n\treturn nil\n}\n\nfunc (s *s3Driver) First() (uint, error) {\n\tv, ok := s.migrations.First()\n\tif !ok {\n\t\treturn 0, os.ErrNotExist\n\t}\n\treturn v, nil\n}\n\nfunc (s *s3Driver) Prev(version uint) (uint, error) {\n\tv, ok := s.migrations.Prev(version)\n\tif !ok {\n\t\treturn 0, os.ErrNotExist\n\t}\n\treturn v, nil\n}\n\nfunc (s *s3Driver) Next(version uint) (uint, error) {\n\tv, ok := s.migrations.Next(version)\n\tif !ok {\n\t\treturn 0, os.ErrNotExist\n\t}\n\treturn v, nil\n}\n\nfunc (s *s3Driver) ReadUp(version uint) (io.ReadCloser, string, error) {\n\tif m, ok := s.migrations.Up(version); ok {\n\t\treturn s.open(m)\n\t}\n\treturn nil, \"\", os.ErrNotExist\n}\n\nfunc (s *s3Driver) ReadDown(version uint) (io.ReadCloser, string, error) {\n\tif m, ok := s.migrations.Down(version); ok {\n\t\treturn s.open(m)\n\t}\n\treturn nil, \"\", os.ErrNotExist\n}\n\nfunc (s *s3Driver) open(m *source.Migration) (io.ReadCloser, string, error) {\n\tkey := path.Join(s.config.Prefix, m.Raw)\n\tobject, err := s.s3client.GetObject(&s3.GetObjectInput{\n\t\tBucket: aws.String(s.config.Bucket),\n\t\tKey:    aws.String(key),\n\t})\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\treturn object.Body, m.Identifier, nil\n}\n"
  },
  {
    "path": "source/aws_s3/s3_test.go",
    "content": "package awss3\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/service/s3\"\n\tst \"github.com/golang-migrate/migrate/v4/source/testing\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test(t *testing.T) {\n\ts3Client := fakeS3{\n\t\tbucket: \"some-bucket\",\n\t\tobjects: map[string]string{\n\t\t\t\"staging/migrations/1_foobar.up.sql\":          \"1 up\",\n\t\t\t\"staging/migrations/1_foobar.down.sql\":        \"1 down\",\n\t\t\t\"prod/migrations/1_foobar.up.sql\":             \"1 up\",\n\t\t\t\"prod/migrations/1_foobar.down.sql\":           \"1 down\",\n\t\t\t\"prod/migrations/3_foobar.up.sql\":             \"3 up\",\n\t\t\t\"prod/migrations/4_foobar.up.sql\":             \"4 up\",\n\t\t\t\"prod/migrations/4_foobar.down.sql\":           \"4 down\",\n\t\t\t\"prod/migrations/5_foobar.down.sql\":           \"5 down\",\n\t\t\t\"prod/migrations/7_foobar.up.sql\":             \"7 up\",\n\t\t\t\"prod/migrations/7_foobar.down.sql\":           \"7 down\",\n\t\t\t\"prod/migrations/not-a-migration.txt\":         \"\",\n\t\t\t\"prod/migrations/0-random-stuff/whatever.txt\": \"\",\n\t\t},\n\t}\n\tdriver, err := WithInstance(&s3Client, &Config{\n\t\tBucket: \"some-bucket\",\n\t\tPrefix: \"prod/migrations/\",\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tst.Test(t, driver)\n}\n\nfunc TestParseURI(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\turi    string\n\t\tconfig *Config\n\t}{\n\t\t{\n\t\t\t\"with prefix, no trailing slash\",\n\t\t\t\"s3://migration-bucket/production\",\n\t\t\t&Config{\n\t\t\t\tBucket: \"migration-bucket\",\n\t\t\t\tPrefix: \"production/\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"without prefix, no trailing slash\",\n\t\t\t\"s3://migration-bucket\",\n\t\t\t&Config{\n\t\t\t\tBucket: \"migration-bucket\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"with prefix, trailing slash\",\n\t\t\t\"s3://migration-bucket/production/\",\n\t\t\t&Config{\n\t\t\t\tBucket: \"migration-bucket\",\n\t\t\t\tPrefix: \"production/\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"without prefix, trailing slash\",\n\t\t\t\"s3://migration-bucket/\",\n\t\t\t&Config{\n\t\t\t\tBucket: \"migration-bucket\",\n\t\t\t},\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tactual, err := parseURI(test.uri)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tassert.Equal(t, test.config, actual)\n\t\t})\n\t}\n}\n\ntype fakeS3 struct {\n\ts3.S3\n\tbucket  string\n\tobjects map[string]string\n}\n\nfunc (s *fakeS3) ListObjects(input *s3.ListObjectsInput) (*s3.ListObjectsOutput, error) {\n\tbucket := aws.StringValue(input.Bucket)\n\tif bucket != s.bucket {\n\t\treturn nil, errors.New(\"bucket not found\")\n\t}\n\tprefix := aws.StringValue(input.Prefix)\n\tdelimiter := aws.StringValue(input.Delimiter)\n\tvar output s3.ListObjectsOutput\n\tfor name := range s.objects {\n\t\tif strings.HasPrefix(name, prefix) {\n\t\t\tif delimiter == \"\" || !strings.Contains(strings.Replace(name, prefix, \"\", 1), delimiter) {\n\t\t\t\toutput.Contents = append(output.Contents, &s3.Object{\n\t\t\t\t\tKey: aws.String(name),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\treturn &output, nil\n}\n\nfunc (s *fakeS3) GetObject(input *s3.GetObjectInput) (*s3.GetObjectOutput, error) {\n\tbucket := aws.StringValue(input.Bucket)\n\tif bucket != s.bucket {\n\t\treturn nil, errors.New(\"bucket not found\")\n\t}\n\tif data, ok := s.objects[aws.StringValue(input.Key)]; ok {\n\t\tbody := io.NopCloser(strings.NewReader(data))\n\t\treturn &s3.GetObjectOutput{Body: body}, nil\n\t}\n\treturn nil, errors.New(\"object not found\")\n}\n"
  },
  {
    "path": "source/bitbucket/.gitignore",
    "content": ".bitbucket_test_secrets\n"
  },
  {
    "path": "source/bitbucket/README.md",
    "content": "# bitbucket\n\nThis driver is catered for those that want to source migrations from bitbucket cloud(https://bitbucket.com).\n\n`bitbucket://user:password@owner/repo/path#ref`\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| user | | The username of the user connecting |\n| password | | User's password or an app password with repo read permission  |\n| owner | | the repo owner |\n| repo | | the name of the repository |\n| path | | path in repo to migrations |\n| ref | | (optional) can be a SHA, branch, or tag |\n"
  },
  {
    "path": "source/bitbucket/bitbucket.go",
    "content": "package bitbucket\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/golang-migrate/migrate/v4/source\"\n\t\"github.com/ktrysmt/go-bitbucket\"\n)\n\nfunc init() {\n\tsource.Register(\"bitbucket\", &Bitbucket{})\n}\n\nvar (\n\tErrNoUserInfo             = fmt.Errorf(\"no username:password provided\")\n\tErrNoAccessToken          = fmt.Errorf(\"no password/app password\")\n\tErrInvalidRepo            = fmt.Errorf(\"invalid repo\")\n\tErrInvalidBitbucketClient = fmt.Errorf(\"expected *bitbucket.Client\")\n\tErrNoDir                  = fmt.Errorf(\"no directory\")\n)\n\ntype Bitbucket struct {\n\tconfig     *Config\n\tclient     *bitbucket.Client\n\tmigrations *source.Migrations\n}\n\ntype Config struct {\n\tOwner string\n\tRepo  string\n\tPath  string\n\tRef   string\n}\n\nfunc (b *Bitbucket) Open(url string) (source.Driver, error) {\n\tu, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif u.User == nil {\n\t\treturn nil, ErrNoUserInfo\n\t}\n\n\tpassword, ok := u.User.Password()\n\tif !ok {\n\t\treturn nil, ErrNoAccessToken\n\t}\n\n\tcl := bitbucket.NewBasicAuth(u.User.Username(), password)\n\n\tcfg := &Config{}\n\t// set owner, repo and path in repo\n\tcfg.Owner = u.Host\n\tpe := strings.Split(strings.Trim(u.Path, \"/\"), \"/\")\n\tif len(pe) < 1 {\n\t\treturn nil, ErrInvalidRepo\n\t}\n\tcfg.Repo = pe[0]\n\tif len(pe) > 1 {\n\t\tcfg.Path = strings.Join(pe[1:], \"/\")\n\t}\n\tcfg.Ref = u.Fragment\n\n\tbi, err := WithInstance(cl, cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn bi, nil\n}\n\nfunc WithInstance(client *bitbucket.Client, config *Config) (source.Driver, error) {\n\tbi := &Bitbucket{\n\t\tclient:     client,\n\t\tconfig:     config,\n\t\tmigrations: source.NewMigrations(),\n\t}\n\n\tif err := bi.readDirectory(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn bi, nil\n}\n\nfunc (b *Bitbucket) readDirectory() error {\n\tb.ensureFields()\n\n\tfOpt := &bitbucket.RepositoryFilesOptions{\n\t\tOwner:    b.config.Owner,\n\t\tRepoSlug: b.config.Repo,\n\t\tRef:      b.config.Ref,\n\t\tPath:     b.config.Path,\n\t}\n\n\tdirContents, err := b.client.Repositories.Repository.ListFiles(fOpt)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, fi := range dirContents {\n\n\t\tm, err := source.DefaultParse(filepath.Base(fi.Path))\n\t\tif err != nil {\n\t\t\tcontinue // ignore files that we can't parse\n\t\t}\n\t\tif !b.migrations.Append(m) {\n\t\t\treturn fmt.Errorf(\"unable to parse file %v\", fi.Path)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (b *Bitbucket) ensureFields() {\n\tif b.config == nil {\n\t\tb.config = &Config{}\n\t}\n}\n\nfunc (b *Bitbucket) Close() error {\n\treturn nil\n}\n\nfunc (b *Bitbucket) First() (version uint, er error) {\n\tb.ensureFields()\n\n\tif v, ok := b.migrations.First(); !ok {\n\t\treturn 0, &os.PathError{Op: \"first\", Path: b.config.Path, Err: os.ErrNotExist}\n\t} else {\n\t\treturn v, nil\n\t}\n}\n\nfunc (b *Bitbucket) Prev(version uint) (prevVersion uint, err error) {\n\tb.ensureFields()\n\n\tif v, ok := b.migrations.Prev(version); !ok {\n\t\treturn 0, &os.PathError{Op: fmt.Sprintf(\"prev for version %v\", version), Path: b.config.Path, Err: os.ErrNotExist}\n\t} else {\n\t\treturn v, nil\n\t}\n}\n\nfunc (b *Bitbucket) Next(version uint) (nextVersion uint, err error) {\n\tb.ensureFields()\n\n\tif v, ok := b.migrations.Next(version); !ok {\n\t\treturn 0, &os.PathError{Op: fmt.Sprintf(\"next for version %v\", version), Path: b.config.Path, Err: os.ErrNotExist}\n\t} else {\n\t\treturn v, nil\n\t}\n}\n\nfunc (b *Bitbucket) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {\n\tb.ensureFields()\n\n\tif m, ok := b.migrations.Up(version); ok {\n\t\tfBlobOpt := &bitbucket.RepositoryBlobOptions{\n\t\t\tOwner:    b.config.Owner,\n\t\t\tRepoSlug: b.config.Repo,\n\t\t\tRef:      b.config.Ref,\n\t\t\tPath:     path.Join(b.config.Path, m.Raw),\n\t\t}\n\t\tfile, err := b.client.Repositories.Repository.GetFileBlob(fBlobOpt)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\t\tif file != nil {\n\t\t\tr := file.Content\n\t\t\treturn io.NopCloser(strings.NewReader(string(r))), m.Identifier, nil\n\t\t}\n\t}\n\treturn nil, \"\", &os.PathError{Op: fmt.Sprintf(\"read version %v\", version), Path: b.config.Path, Err: os.ErrNotExist}\n}\n\nfunc (b *Bitbucket) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {\n\tb.ensureFields()\n\n\tif m, ok := b.migrations.Down(version); ok {\n\t\tfBlobOpt := &bitbucket.RepositoryBlobOptions{\n\t\t\tOwner:    b.config.Owner,\n\t\t\tRepoSlug: b.config.Repo,\n\t\t\tRef:      b.config.Ref,\n\t\t\tPath:     path.Join(b.config.Path, m.Raw),\n\t\t}\n\t\tfile, err := b.client.Repositories.Repository.GetFileBlob(fBlobOpt)\n\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\t\tif file != nil {\n\t\t\tr := file.Content\n\n\t\t\treturn io.NopCloser(strings.NewReader(string(r))), m.Identifier, nil\n\t\t}\n\t}\n\treturn nil, \"\", &os.PathError{Op: fmt.Sprintf(\"read version %v\", version), Path: b.config.Path, Err: os.ErrNotExist}\n}\n"
  },
  {
    "path": "source/bitbucket/bitbucket_test.go",
    "content": "package bitbucket\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"testing\"\n\n\tst \"github.com/golang-migrate/migrate/v4/source/testing\"\n)\n\nvar BitbucketTestSecret = \"\" // username:password\n\nfunc init() {\n\tsecrets, err := os.ReadFile(\".bitbucket_test_secrets\")\n\tif err == nil {\n\t\tBitbucketTestSecret = string(bytes.TrimSpace(secrets)[:])\n\t}\n}\n\nfunc Test(t *testing.T) {\n\tif len(BitbucketTestSecret) == 0 {\n\t\tt.Skip(\"test requires .bitbucket_test_secrets\")\n\t}\n\n\tb := &Bitbucket{}\n\n\td, err := b.Open(\"bitbucket://\" + BitbucketTestSecret + \"@abhishekbipp/test-migration/migrations/test#master\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tst.Test(t, d)\n}\n"
  },
  {
    "path": "source/driver.go",
    "content": "// Package source provides the Source interface.\n// All source drivers must implement this interface, register themselves,\n// optionally provide a `WithInstance` function and pass the tests\n// in package source/testing.\npackage source\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"sync\"\n)\n\nvar driversMu sync.RWMutex\nvar drivers = make(map[string]Driver)\n\n// Driver is the interface every source driver must implement.\n//\n// How to implement a source driver?\n//  1. Implement this interface.\n//  2. Optionally, add a function named `WithInstance`.\n//     This function should accept an existing source instance and a Config{} struct\n//     and return a driver instance.\n//  3. Add a test that calls source/testing.go:Test()\n//  4. Add own tests for Open(), WithInstance() (when provided) and Close().\n//     All other functions are tested by tests in source/testing.\n//     Saves you some time and makes sure all source drivers behave the same way.\n//  5. Call Register in init().\n//\n// Guidelines:\n//   - All configuration input must come from the URL string in func Open()\n//     or the Config{} struct in WithInstance. Don't os.Getenv().\n//   - Drivers are supposed to be read only.\n//   - Ideally don't load any contents (into memory) in Open or WithInstance.\ntype Driver interface {\n\t// Open returns a new driver instance configured with parameters\n\t// coming from the URL string. Migrate will call this function\n\t// only once per instance.\n\tOpen(url string) (Driver, error)\n\n\t// Close closes the underlying source instance managed by the driver.\n\t// Migrate will call this function only once per instance.\n\tClose() error\n\n\t// First returns the very first migration version available to the driver.\n\t// Migrate will call this function multiple times.\n\t// If there is no version available, it must return os.ErrNotExist.\n\tFirst() (version uint, err error)\n\n\t// Prev returns the previous version for a given version available to the driver.\n\t// Migrate will call this function multiple times.\n\t// If there is no previous version available, it must return os.ErrNotExist.\n\tPrev(version uint) (prevVersion uint, err error)\n\n\t// Next returns the next version for a given version available to the driver.\n\t// Migrate will call this function multiple times.\n\t// If there is no next version available, it must return os.ErrNotExist.\n\tNext(version uint) (nextVersion uint, err error)\n\n\t// ReadUp returns the UP migration body and an identifier that helps\n\t// finding this migration in the source for a given version.\n\t// If there is no up migration available for this version,\n\t// it must return os.ErrNotExist.\n\t// Do not start reading, just return the ReadCloser!\n\tReadUp(version uint) (r io.ReadCloser, identifier string, err error)\n\n\t// ReadDown returns the DOWN migration body and an identifier that helps\n\t// finding this migration in the source for a given version.\n\t// If there is no down migration available for this version,\n\t// it must return os.ErrNotExist.\n\t// Do not start reading, just return the ReadCloser!\n\tReadDown(version uint) (r io.ReadCloser, identifier string, err error)\n}\n\n// Open returns a new driver instance.\nfunc Open(url string) (Driver, error) {\n\tu, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif u.Scheme == \"\" {\n\t\treturn nil, fmt.Errorf(\"source driver: invalid URL scheme\")\n\t}\n\n\tdriversMu.RLock()\n\td, ok := drivers[u.Scheme]\n\tdriversMu.RUnlock()\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"source driver: unknown driver '%s' (forgotten import?)\", u.Scheme)\n\t}\n\n\treturn d.Open(url)\n}\n\n// Register globally registers a driver.\nfunc Register(name string, driver Driver) {\n\tdriversMu.Lock()\n\tdefer driversMu.Unlock()\n\tif driver == nil {\n\t\tpanic(\"Register driver is nil\")\n\t}\n\tif _, dup := drivers[name]; dup {\n\t\tpanic(\"Register called twice for driver \" + name)\n\t}\n\tdrivers[name] = driver\n}\n\n// List lists the registered drivers\nfunc List() []string {\n\tdriversMu.RLock()\n\tdefer driversMu.RUnlock()\n\tnames := make([]string, 0, len(drivers))\n\tfor n := range drivers {\n\t\tnames = append(names, n)\n\t}\n\treturn names\n}\n"
  },
  {
    "path": "source/driver_test.go",
    "content": "package source\n\nfunc ExampleDriver() {\n\t// see source/stub for an example\n\n\t// source/stub/stub.go has the driver implementation\n\t// source/stub/stub_test.go runs source/testing/test.go:Test\n}\n"
  },
  {
    "path": "source/errors.go",
    "content": "package source\n\nimport \"os\"\n\n// ErrDuplicateMigration is an error type for reporting duplicate migration\n// files.\ntype ErrDuplicateMigration struct {\n\tMigration\n\tos.FileInfo\n}\n\n// Error implements error interface.\nfunc (e ErrDuplicateMigration) Error() string {\n\treturn \"duplicate migration file: \" + e.Name()\n}\n"
  },
  {
    "path": "source/file/README.md",
    "content": "# file\n\n`file:///absolute/path`  \n`file://relative/path`\n"
  },
  {
    "path": "source/file/file.go",
    "content": "package file\n\nimport (\n\tnurl \"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/golang-migrate/migrate/v4/source\"\n\t\"github.com/golang-migrate/migrate/v4/source/iofs\"\n)\n\nfunc init() {\n\tsource.Register(\"file\", &File{})\n}\n\ntype File struct {\n\tiofs.PartialDriver\n\turl  string\n\tpath string\n}\n\nfunc (f *File) Open(url string) (source.Driver, error) {\n\tp, err := parseURL(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnf := &File{\n\t\turl:  url,\n\t\tpath: p,\n\t}\n\tif err := nf.Init(os.DirFS(p), \".\"); err != nil {\n\t\treturn nil, err\n\t}\n\treturn nf, nil\n}\n\nfunc parseURL(url string) (string, error) {\n\tu, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\t// concat host and path to restore full path\n\t// host might be `.`\n\tp := u.Opaque\n\tif len(p) == 0 {\n\t\tp = u.Host + u.Path\n\t}\n\n\tif len(p) == 0 {\n\t\t// default to current directory if no path\n\t\twd, err := os.Getwd()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tp = wd\n\n\t} else if p[0:1] == \".\" || p[0:1] != \"/\" {\n\t\t// make path absolute if relative\n\t\tabs, err := filepath.Abs(p)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tp = abs\n\t}\n\treturn p, nil\n}\n"
  },
  {
    "path": "source/file/file_test.go",
    "content": "package file\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\tst \"github.com/golang-migrate/migrate/v4/source/testing\"\n)\n\nconst scheme = \"file://\"\n\nfunc Test(t *testing.T) {\n\ttmpDir := t.TempDir()\n\n\t// write files that meet driver test requirements\n\tmustWriteFile(t, tmpDir, \"1_foobar.up.sql\", \"1 up\")\n\tmustWriteFile(t, tmpDir, \"1_foobar.down.sql\", \"1 down\")\n\n\tmustWriteFile(t, tmpDir, \"3_foobar.up.sql\", \"3 up\")\n\n\tmustWriteFile(t, tmpDir, \"4_foobar.up.sql\", \"4 up\")\n\tmustWriteFile(t, tmpDir, \"4_foobar.down.sql\", \"4 down\")\n\n\tmustWriteFile(t, tmpDir, \"5_foobar.down.sql\", \"5 down\")\n\n\tmustWriteFile(t, tmpDir, \"7_foobar.up.sql\", \"7 up\")\n\tmustWriteFile(t, tmpDir, \"7_foobar.down.sql\", \"7 down\")\n\n\tf := &File{}\n\td, err := f.Open(scheme + tmpDir)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tst.Test(t, d)\n}\n\nfunc TestOpen(t *testing.T) {\n\ttmpDir := t.TempDir()\n\n\tmustWriteFile(t, tmpDir, \"1_foobar.up.sql\", \"\")\n\tmustWriteFile(t, tmpDir, \"1_foobar.down.sql\", \"\")\n\n\tif !filepath.IsAbs(tmpDir) {\n\t\tt.Fatal(\"expected tmpDir to be absolute path\")\n\t}\n\n\tf := &File{}\n\t_, err := f.Open(scheme + tmpDir) // absolute path\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestOpenWithRelativePath(t *testing.T) {\n\ttmpDir := t.TempDir()\n\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\t// rescue working dir after we are done\n\t\tif err := os.Chdir(wd); err != nil {\n\t\t\tt.Log(err)\n\t\t}\n\t}()\n\n\tif err := os.Chdir(tmpDir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := os.Mkdir(filepath.Join(tmpDir, \"foo\"), os.ModePerm); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tmustWriteFile(t, filepath.Join(tmpDir, \"foo\"), \"1_foobar.up.sql\", \"\")\n\n\tf := &File{}\n\n\t// dir: foo\n\td, err := f.Open(\"file://foo\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t_, err = d.First()\n\tif err != nil {\n\t\tt.Fatalf(\"expected first file in working dir %v for foo\", tmpDir)\n\t}\n\n\t// dir: ./foo\n\td, err = f.Open(\"file://./foo\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t_, err = d.First()\n\tif err != nil {\n\t\tt.Fatalf(\"expected first file in working dir %v for ./foo\", tmpDir)\n\t}\n}\n\nfunc TestOpenDefaultsToCurrentDirectory(t *testing.T) {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tf := &File{}\n\td, err := f.Open(scheme)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif d.(*File).path != wd {\n\t\tt.Fatal(\"expected driver to default to current directory\")\n\t}\n}\n\nfunc TestOpenWithDuplicateVersion(t *testing.T) {\n\ttmpDir := t.TempDir()\n\n\tmustWriteFile(t, tmpDir, \"1_foo.up.sql\", \"\") // 1 up\n\tmustWriteFile(t, tmpDir, \"1_bar.up.sql\", \"\") // 1 up\n\n\tf := &File{}\n\t_, err := f.Open(scheme + tmpDir)\n\tif err == nil {\n\t\tt.Fatal(\"expected err\")\n\t}\n}\n\nfunc TestClose(t *testing.T) {\n\ttmpDir := t.TempDir()\n\n\tf := &File{}\n\td, err := f.Open(scheme + tmpDir)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif d.Close() != nil {\n\t\tt.Fatal(\"expected nil\")\n\t}\n}\n\nfunc mustWriteFile(t testing.TB, dir, file string, body string) {\n\tif err := os.WriteFile(path.Join(dir, file), []byte(body), 06444); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc mustCreateBenchmarkDir(t *testing.B) (dir string) {\n\ttmpDir := t.TempDir()\n\n\tfor i := 0; i < 1000; i++ {\n\t\tmustWriteFile(t, tmpDir, fmt.Sprintf(\"%v_foobar.up.sql\", i), \"\")\n\t\tmustWriteFile(t, tmpDir, fmt.Sprintf(\"%v_foobar.down.sql\", i), \"\")\n\t}\n\n\treturn tmpDir\n}\n\nfunc BenchmarkOpen(b *testing.B) {\n\tdir := mustCreateBenchmarkDir(b)\n\tdefer func() {\n\t\tif err := os.RemoveAll(dir); err != nil {\n\t\t\tb.Error(err)\n\t\t}\n\t}()\n\tb.ResetTimer()\n\tfor n := 0; n < b.N; n++ {\n\t\tf := &File{}\n\t\t_, err := f.Open(scheme + dir)\n\t\tif err != nil {\n\t\t\tb.Error(err)\n\t\t}\n\t}\n\tb.StopTimer()\n}\n\nfunc BenchmarkNext(b *testing.B) {\n\tdir := mustCreateBenchmarkDir(b)\n\tdefer func() {\n\t\tif err := os.RemoveAll(dir); err != nil {\n\t\t\tb.Error(err)\n\t\t}\n\t}()\n\tf := &File{}\n\td, _ := f.Open(scheme + dir)\n\tb.ResetTimer()\n\tv, err := d.First()\n\tfor n := 0; n < b.N; n++ {\n\t\tfor !errors.Is(err, os.ErrNotExist) {\n\t\t\tv, err = d.Next(v)\n\t\t}\n\t}\n\tb.StopTimer()\n}\n"
  },
  {
    "path": "source/github/.gitignore",
    "content": ".github_test_secrets\n"
  },
  {
    "path": "source/github/README.md",
    "content": "# github\r\n\r\nThis driver is catered for those that want to source migrations from [github.com](https://github.com). The URL scheme doesn't require a hostname, as it just simply defaults to `github.com`.\r\n\r\nAuthenticated client: `github://user:personal-access-token@owner/repo/path#ref`\r\n\r\nUnauthenticated client: `github://owner/repo/path#ref`\r\n\r\n| URL Query  | WithInstance Config | Description |\r\n|------------|---------------------|-------------|\r\n| user | | (optional) The username of the user connecting |\r\n| personal-access-token | | (optional) An access token from GitHub (https://github.com/settings/tokens) |\r\n| owner | | the repo owner |\r\n| repo | | the name of the repository |\r\n| path | | path in repo to migrations |\r\n| ref | | (optional) can be a SHA, branch, or tag |\r\n"
  },
  {
    "path": "source/github/examples/migrations/1085649617_create_users_table.down.sql",
    "content": "DROP TABLE IF EXISTS users;\n"
  },
  {
    "path": "source/github/examples/migrations/1085649617_create_users_table.up.sql",
    "content": "CREATE TABLE users (\n  user_id integer unique,\n  name    varchar(40),\n  email   varchar(40)\n);\n"
  },
  {
    "path": "source/github/examples/migrations/1185749658_add_city_to_users.down.sql",
    "content": "ALTER TABLE users DROP COLUMN IF EXISTS city;\n"
  },
  {
    "path": "source/github/examples/migrations/1185749658_add_city_to_users.up.sql",
    "content": "ALTER TABLE users ADD COLUMN city varchar(100);\n\n\n"
  },
  {
    "path": "source/github/examples/migrations/1285849751_add_index_on_user_emails.down.sql",
    "content": "DROP INDEX IF EXISTS users_email_index;\n"
  },
  {
    "path": "source/github/examples/migrations/1285849751_add_index_on_user_emails.up.sql",
    "content": "CREATE UNIQUE INDEX CONCURRENTLY users_email_index ON users (email);\n\n-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "source/github/examples/migrations/1385949617_create_books_table.down.sql",
    "content": "DROP TABLE IF EXISTS books;\n"
  },
  {
    "path": "source/github/examples/migrations/1385949617_create_books_table.up.sql",
    "content": "CREATE TABLE books (\n  user_id integer,\n  name    varchar(40),\n  author  varchar(40)\n);\n"
  },
  {
    "path": "source/github/examples/migrations/1485949617_create_movies_table.down.sql",
    "content": "DROP TABLE IF EXISTS movies;\n"
  },
  {
    "path": "source/github/examples/migrations/1485949617_create_movies_table.up.sql",
    "content": "CREATE TABLE movies (\n  user_id   integer,\n  name      varchar(40),\n  director  varchar(40)\n);\n"
  },
  {
    "path": "source/github/examples/migrations/1585849751_just_a_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "source/github/examples/migrations/1685849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "source/github/examples/migrations/1785849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "source/github/examples/migrations/1885849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "source/github/github.go",
    "content": "package github\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\tnurl \"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\n\t\"golang.org/x/oauth2\"\n\n\t\"github.com/golang-migrate/migrate/v4/source\"\n\t\"github.com/google/go-github/v39/github\"\n)\n\nfunc init() {\n\tsource.Register(\"github\", &Github{})\n}\n\nvar (\n\tErrNoUserInfo          = fmt.Errorf(\"no username:token provided\")\n\tErrNoAccessToken       = fmt.Errorf(\"no access token\")\n\tErrInvalidRepo         = fmt.Errorf(\"invalid repo\")\n\tErrInvalidGithubClient = fmt.Errorf(\"expected *github.Client\")\n\tErrNoDir               = fmt.Errorf(\"no directory\")\n)\n\ntype Github struct {\n\tconfig     *Config\n\tclient     *github.Client\n\toptions    *github.RepositoryContentGetOptions\n\tmigrations *source.Migrations\n}\n\ntype Config struct {\n\tOwner string\n\tRepo  string\n\tPath  string\n\tRef   string\n}\n\nfunc (g *Github) Open(url string) (source.Driver, error) {\n\tu, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// client defaults to http.DefaultClient\n\tvar client *http.Client\n\tif u.User != nil {\n\t\tpassword, ok := u.User.Password()\n\t\tif !ok {\n\t\t\treturn nil, ErrNoUserInfo\n\t\t}\n\t\tts := oauth2.StaticTokenSource(\n\t\t\t&oauth2.Token{AccessToken: password},\n\t\t)\n\t\tclient = oauth2.NewClient(context.Background(), ts)\n\n\t}\n\n\tgn := &Github{\n\t\tclient:     github.NewClient(client),\n\t\tmigrations: source.NewMigrations(),\n\t\toptions:    &github.RepositoryContentGetOptions{Ref: u.Fragment},\n\t}\n\n\tgn.ensureFields()\n\n\t// set owner, repo and path in repo\n\tgn.config.Owner = u.Host\n\tpe := strings.Split(strings.Trim(u.Path, \"/\"), \"/\")\n\tif len(pe) < 1 {\n\t\treturn nil, ErrInvalidRepo\n\t}\n\tgn.config.Repo = pe[0]\n\tif len(pe) > 1 {\n\t\tgn.config.Path = strings.Join(pe[1:], \"/\")\n\t}\n\n\tif err := gn.readDirectory(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn gn, nil\n}\n\nfunc WithInstance(client *github.Client, config *Config) (source.Driver, error) {\n\tgn := &Github{\n\t\tclient:     client,\n\t\tconfig:     config,\n\t\tmigrations: source.NewMigrations(),\n\t\toptions:    &github.RepositoryContentGetOptions{Ref: config.Ref},\n\t}\n\n\tif err := gn.readDirectory(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn gn, nil\n}\n\nfunc (g *Github) readDirectory() error {\n\tg.ensureFields()\n\n\tfileContent, dirContents, _, err := g.client.Repositories.GetContents(\n\t\tcontext.Background(),\n\t\tg.config.Owner,\n\t\tg.config.Repo,\n\t\tg.config.Path,\n\t\tg.options,\n\t)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\tif fileContent != nil {\n\t\treturn ErrNoDir\n\t}\n\n\tfor _, fi := range dirContents {\n\t\tm, err := source.DefaultParse(*fi.Name)\n\t\tif err != nil {\n\t\t\tcontinue // ignore files that we can't parse\n\t\t}\n\t\tif !g.migrations.Append(m) {\n\t\t\treturn fmt.Errorf(\"unable to parse file %v\", *fi.Name)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (g *Github) ensureFields() {\n\tif g.config == nil {\n\t\tg.config = &Config{}\n\t}\n}\n\nfunc (g *Github) Close() error {\n\treturn nil\n}\n\nfunc (g *Github) First() (version uint, err error) {\n\tg.ensureFields()\n\n\tif v, ok := g.migrations.First(); !ok {\n\t\treturn 0, &os.PathError{Op: \"first\", Path: g.config.Path, Err: os.ErrNotExist}\n\t} else {\n\t\treturn v, nil\n\t}\n}\n\nfunc (g *Github) Prev(version uint) (prevVersion uint, err error) {\n\tg.ensureFields()\n\n\tif v, ok := g.migrations.Prev(version); !ok {\n\t\treturn 0, &os.PathError{Op: fmt.Sprintf(\"prev for version %v\", version), Path: g.config.Path, Err: os.ErrNotExist}\n\t} else {\n\t\treturn v, nil\n\t}\n}\n\nfunc (g *Github) Next(version uint) (nextVersion uint, err error) {\n\tg.ensureFields()\n\n\tif v, ok := g.migrations.Next(version); !ok {\n\t\treturn 0, &os.PathError{Op: fmt.Sprintf(\"next for version %v\", version), Path: g.config.Path, Err: os.ErrNotExist}\n\t} else {\n\t\treturn v, nil\n\t}\n}\n\nfunc (g *Github) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {\n\tg.ensureFields()\n\n\tif m, ok := g.migrations.Up(version); ok {\n\t\tr, _, err := g.client.Repositories.DownloadContents(\n\t\t\tcontext.Background(),\n\t\t\tg.config.Owner,\n\t\t\tg.config.Repo,\n\t\t\tpath.Join(g.config.Path, m.Raw),\n\t\t\tg.options,\n\t\t)\n\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\t\treturn r, m.Identifier, nil\n\t}\n\treturn nil, \"\", &os.PathError{Op: fmt.Sprintf(\"read version %v\", version), Path: g.config.Path, Err: os.ErrNotExist}\n}\n\nfunc (g *Github) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {\n\tg.ensureFields()\n\n\tif m, ok := g.migrations.Down(version); ok {\n\t\tr, _, err := g.client.Repositories.DownloadContents(\n\t\t\tcontext.Background(),\n\t\t\tg.config.Owner,\n\t\t\tg.config.Repo,\n\t\t\tpath.Join(g.config.Path, m.Raw),\n\t\t\tg.options,\n\t\t)\n\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\t\treturn r, m.Identifier, nil\n\t}\n\treturn nil, \"\", &os.PathError{Op: fmt.Sprintf(\"read version %v\", version), Path: g.config.Path, Err: os.ErrNotExist}\n}\n"
  },
  {
    "path": "source/github/github_test.go",
    "content": "package github\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\tst \"github.com/golang-migrate/migrate/v4/source/testing\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar GithubTestSecret = \"\" // username:token\n\nfunc init() {\n\tsecrets, err := os.ReadFile(\".github_test_secrets\")\n\tif err == nil {\n\t\tGithubTestSecret = string(bytes.TrimSpace(secrets)[:])\n\t}\n}\n\nfunc Test(t *testing.T) {\n\tif len(GithubTestSecret) == 0 {\n\t\tt.Skip(\"test requires .github_test_secrets\")\n\t}\n\n\tg := &Github{}\n\td, err := g.Open(\"github://\" + GithubTestSecret + \"@mattes/migrate_test_tmp/test#452b8003e7\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tst.Test(t, d)\n}\n\nfunc TestDefaultClient(t *testing.T) {\n\tg := &Github{}\n\towner := \"golang-migrate\"\n\trepo := \"migrate\"\n\tpath := \"source/github/examples/migrations\"\n\n\turl := fmt.Sprintf(\"github://%s/%s/%s\", owner, repo, path)\n\td, err := g.Open(url)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tver, err := d.First()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.Equal(t, uint(1085649617), ver)\n\n\tver, err = d.Next(ver)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.Equal(t, uint(1185749658), ver)\n}\n"
  },
  {
    "path": "source/github_ee/.gitignore",
    "content": ".github_test_secrets\n"
  },
  {
    "path": "source/github_ee/README.md",
    "content": "# github ee\n\n## GitHub Enterprise Edition\n\nThis driver is catered for those who run GitHub Enterprise under private infrastructure.\n\nThe below URL scheme illustrates how to source migration files from GitHub Enterprise.\n\nGitHub client for Go requires API and Uploads endpoint hosts in order to create an instance of GitHub Enterprise Client. We're making an assumption that the API and Uploads are available under `https://api.*` and `https://uploads.*` respectively. [GitHub Enterprise Installation Guide](https://help.github.com/en/enterprise/2.15/admin/installation/enabling-subdomain-isolation) recommends that you enable Subdomain isolation feature.\n\n`github-ee://user:personal-access-token@host/owner/repo/path?verify-tls=true#ref`\n\n| URL Query  | WithInstance Config | Description |\n|------------|---------------------|-------------|\n| user | | The username of the user connecting |\n| personal-access-token | | Personal access token from your GitHub Enterprise instance |\n| owner | | the repo owner |\n| repo | | the name of the repository |\n| path | | path in repo to migrations |\n| ref | | (optional) can be a SHA, branch, or tag |\n| verify-tls | | (optional) defaults to `true`. This option sets `tls.Config.InsecureSkipVerify` accordingly |\n"
  },
  {
    "path": "source/github_ee/github_ee.go",
    "content": "package github_ee\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net/http\"\n\tnurl \"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/golang-migrate/migrate/v4/source\"\n\tgh \"github.com/golang-migrate/migrate/v4/source/github\"\n\n\t\"github.com/google/go-github/v39/github\"\n)\n\nfunc init() {\n\tsource.Register(\"github-ee\", &GithubEE{})\n}\n\ntype GithubEE struct {\n\tsource.Driver\n}\n\nfunc (g *GithubEE) Open(url string) (source.Driver, error) {\n\tverifyTLS := true\n\n\tu, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif o := u.Query().Get(\"verify-tls\"); o != \"\" {\n\t\tverifyTLS = parseBool(o, verifyTLS)\n\t}\n\n\tif u.User == nil {\n\t\treturn nil, gh.ErrNoUserInfo\n\t}\n\n\tpassword, ok := u.User.Password()\n\tif !ok {\n\t\treturn nil, gh.ErrNoUserInfo\n\t}\n\n\tghc, err := g.createGithubClient(u.Host, u.User.Username(), password, verifyTLS)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpe := strings.Split(strings.Trim(u.Path, \"/\"), \"/\")\n\n\tif len(pe) < 1 {\n\t\treturn nil, gh.ErrInvalidRepo\n\t}\n\n\tcfg := &gh.Config{\n\t\tOwner: pe[0],\n\t\tRepo:  pe[1],\n\t\tRef:   u.Fragment,\n\t}\n\n\tif len(pe) > 2 {\n\t\tcfg.Path = strings.Join(pe[2:], \"/\")\n\t}\n\n\ti, err := gh.WithInstance(ghc, cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &GithubEE{Driver: i}, nil\n}\n\nfunc (g *GithubEE) createGithubClient(host, username, password string, verifyTLS bool) (*github.Client, error) {\n\ttr := &github.BasicAuthTransport{\n\t\tUsername: username,\n\t\tPassword: password,\n\t\tTransport: &http.Transport{\n\t\t\tTLSClientConfig: &tls.Config{InsecureSkipVerify: !verifyTLS},\n\t\t},\n\t}\n\n\tapiHost := fmt.Sprintf(\"https://%s/api/v3\", host)\n\tuploadHost := fmt.Sprintf(\"https://uploads.%s\", host)\n\n\treturn github.NewEnterpriseClient(apiHost, uploadHost, tr.Client())\n}\n\nfunc parseBool(val string, fallback bool) bool {\n\tb, err := strconv.ParseBool(val)\n\tif err != nil {\n\t\treturn fallback\n\t}\n\n\treturn b\n}\n"
  },
  {
    "path": "source/github_ee/github_ee_test.go",
    "content": "package github_ee\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\tnurl \"net/url\"\n\t\"testing\"\n)\n\nfunc Test(t *testing.T) {\n\tts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.URL.Path != \"/api/v3/repos/mattes/migrate_test_tmp/contents/test\" {\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t\treturn\n\t\t}\n\n\t\tif ref := r.URL.Query().Get(\"ref\"); ref != \"452b8003e7\" {\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t\treturn\n\t\t}\n\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.WriteHeader(http.StatusOK)\n\n\t\t_, err := w.Write([]byte(\"[]\"))\n\t\tif err != nil {\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\tu, err := nurl.Parse(ts.URL)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tg := &GithubEE{}\n\t_, err = g.Open(\"github-ee://foo:bar@\" + u.Host + \"/mattes/migrate_test_tmp/test?verify-tls=false#452b8003e7\")\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "source/gitlab/.gitignore",
    "content": ".gitlab_test_secrets\n"
  },
  {
    "path": "source/gitlab/README.md",
    "content": "# gitlab\r\n\r\n`gitlab://user:personal-access-token@gitlab_url/project_id/path#ref`\r\n\r\n| URL Query  | WithInstance Config | Description |\r\n|------------|---------------------|-------------|\r\n| user | | The username of the user connecting |\r\n| personal-access-token | | An access token from Gitlab (https://<gitlab_url>/profile/personal_access_tokens) |\r\n| gitlab_url | | url of the gitlab server |\r\n| project_id | | id of the repository |\r\n| path | | path in repo to migrations |\r\n| ref | | (optional) can be a SHA, branch, or tag |\r\n"
  },
  {
    "path": "source/gitlab/examples/migrations/1085649617_create_users_table.down.sql",
    "content": "DROP TABLE IF EXISTS users;\n"
  },
  {
    "path": "source/gitlab/examples/migrations/1085649617_create_users_table.up.sql",
    "content": "CREATE TABLE users (\n  user_id integer unique,\n  name    varchar(40),\n  email   varchar(40)\n);\n"
  },
  {
    "path": "source/gitlab/examples/migrations/1185749658_add_city_to_users.down.sql",
    "content": "ALTER TABLE users DROP COLUMN IF EXISTS city;\n"
  },
  {
    "path": "source/gitlab/examples/migrations/1185749658_add_city_to_users.up.sql",
    "content": "ALTER TABLE users ADD COLUMN city varchar(100);\n\n\n"
  },
  {
    "path": "source/gitlab/examples/migrations/1285849751_add_index_on_user_emails.down.sql",
    "content": "DROP INDEX IF EXISTS users_email_index;\n"
  },
  {
    "path": "source/gitlab/examples/migrations/1285849751_add_index_on_user_emails.up.sql",
    "content": "CREATE UNIQUE INDEX CONCURRENTLY users_email_index ON users (email);\n\n-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "source/gitlab/examples/migrations/1385949617_create_books_table.down.sql",
    "content": "DROP TABLE IF EXISTS books;\n"
  },
  {
    "path": "source/gitlab/examples/migrations/1385949617_create_books_table.up.sql",
    "content": "CREATE TABLE books (\n  user_id integer,\n  name    varchar(40),\n  author  varchar(40)\n);\n"
  },
  {
    "path": "source/gitlab/examples/migrations/1485949617_create_movies_table.down.sql",
    "content": "DROP TABLE IF EXISTS movies;\n"
  },
  {
    "path": "source/gitlab/examples/migrations/1485949617_create_movies_table.up.sql",
    "content": "CREATE TABLE movies (\n  user_id   integer,\n  name      varchar(40),\n  director  varchar(40)\n);\n"
  },
  {
    "path": "source/gitlab/examples/migrations/1585849751_just_a_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "source/gitlab/examples/migrations/1685849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "source/gitlab/examples/migrations/1785849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "source/gitlab/examples/migrations/1885849751_another_comment.up.sql",
    "content": "-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.\n"
  },
  {
    "path": "source/gitlab/gitlab.go",
    "content": "package gitlab\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\tnurl \"net/url\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/golang-migrate/migrate/v4/source\"\n\t\"github.com/xanzy/go-gitlab\"\n)\n\nfunc init() {\n\tsource.Register(\"gitlab\", &Gitlab{})\n}\n\nconst DefaultMaxItemsPerPage = 100\n\nvar (\n\tErrNoUserInfo       = fmt.Errorf(\"no username:token provided\")\n\tErrNoAccessToken    = fmt.Errorf(\"no access token\")\n\tErrInvalidHost      = fmt.Errorf(\"invalid host\")\n\tErrInvalidProjectID = fmt.Errorf(\"invalid project id\")\n\tErrInvalidResponse  = fmt.Errorf(\"invalid response\")\n)\n\ntype Gitlab struct {\n\tclient *gitlab.Client\n\turl    string\n\n\tprojectID   string\n\tpath        string\n\tlistOptions *gitlab.ListTreeOptions\n\tgetOptions  *gitlab.GetFileOptions\n\tmigrations  *source.Migrations\n}\n\ntype Config struct {\n}\n\nfunc (g *Gitlab) Open(url string) (source.Driver, error) {\n\tu, err := nurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif u.User == nil {\n\t\treturn nil, ErrNoUserInfo\n\t}\n\n\tpassword, ok := u.User.Password()\n\tif !ok {\n\t\treturn nil, ErrNoAccessToken\n\t}\n\n\tgn := &Gitlab{\n\t\tclient:     gitlab.NewClient(nil, password),\n\t\turl:        url,\n\t\tmigrations: source.NewMigrations(),\n\t}\n\n\tif u.Host != \"\" {\n\t\turi := nurl.URL{\n\t\t\tScheme: \"https\",\n\t\t\tHost:   u.Host,\n\t\t}\n\n\t\terr = gn.client.SetBaseURL(uri.String())\n\t\tif err != nil {\n\t\t\treturn nil, ErrInvalidHost\n\t\t}\n\t}\n\n\tpe := strings.Split(strings.Trim(u.Path, \"/\"), \"/\")\n\tif len(pe) < 1 {\n\t\treturn nil, ErrInvalidProjectID\n\t}\n\tgn.projectID = pe[0]\n\tif len(pe) > 1 {\n\t\tgn.path = strings.Join(pe[1:], \"/\")\n\t}\n\n\tgn.listOptions = &gitlab.ListTreeOptions{\n\t\tPath: &gn.path,\n\t\tRef:  &u.Fragment,\n\t\tListOptions: gitlab.ListOptions{\n\t\t\tPerPage: DefaultMaxItemsPerPage,\n\t\t},\n\t}\n\n\tgn.getOptions = &gitlab.GetFileOptions{\n\t\tRef: &u.Fragment,\n\t}\n\n\tif err := gn.readDirectory(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn gn, nil\n}\n\nfunc WithInstance(client *gitlab.Client, config *Config) (source.Driver, error) {\n\tgn := &Gitlab{\n\t\tclient:     client,\n\t\tmigrations: source.NewMigrations(),\n\t}\n\tif err := gn.readDirectory(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn gn, nil\n}\n\nfunc (g *Gitlab) readDirectory() error {\n\tvar nodes []*gitlab.TreeNode\n\tfor {\n\t\tn, response, err := g.client.Repositories.ListTree(g.projectID, g.listOptions)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif response.StatusCode != http.StatusOK {\n\t\t\treturn ErrInvalidResponse\n\t\t}\n\n\t\tnodes = append(nodes, n...)\n\t\tif response.CurrentPage >= response.TotalPages {\n\t\t\tbreak\n\t\t}\n\t\tg.listOptions.Page = response.NextPage\n\t}\n\n\tfor i := range nodes {\n\t\tm, err := g.nodeToMigration(nodes[i])\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif !g.migrations.Append(m) {\n\t\t\treturn fmt.Errorf(\"unable to parse file %v\", nodes[i].Name)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (g *Gitlab) nodeToMigration(node *gitlab.TreeNode) (*source.Migration, error) {\n\tm := source.Regex.FindStringSubmatch(node.Name)\n\tif len(m) == 5 {\n\t\tversionUint64, err := strconv.ParseUint(m[1], 10, 64)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &source.Migration{\n\t\t\tVersion:    uint(versionUint64),\n\t\t\tIdentifier: m[2],\n\t\t\tDirection:  source.Direction(m[3]),\n\t\t\tRaw:        g.path + \"/\" + node.Name,\n\t\t}, nil\n\t}\n\treturn nil, source.ErrParse\n}\n\nfunc (g *Gitlab) Close() error {\n\treturn nil\n}\n\nfunc (g *Gitlab) First() (version uint, er error) {\n\tif v, ok := g.migrations.First(); !ok {\n\t\treturn 0, &os.PathError{Op: \"first\", Path: g.path, Err: os.ErrNotExist}\n\t} else {\n\t\treturn v, nil\n\t}\n}\n\nfunc (g *Gitlab) Prev(version uint) (prevVersion uint, err error) {\n\tif v, ok := g.migrations.Prev(version); !ok {\n\t\treturn 0, &os.PathError{Op: fmt.Sprintf(\"prev for version %v\", version), Path: g.path, Err: os.ErrNotExist}\n\t} else {\n\t\treturn v, nil\n\t}\n}\n\nfunc (g *Gitlab) Next(version uint) (nextVersion uint, err error) {\n\tif v, ok := g.migrations.Next(version); !ok {\n\t\treturn 0, &os.PathError{Op: fmt.Sprintf(\"next for version %v\", version), Path: g.path, Err: os.ErrNotExist}\n\t} else {\n\t\treturn v, nil\n\t}\n}\n\nfunc (g *Gitlab) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {\n\tif m, ok := g.migrations.Up(version); ok {\n\t\tf, response, err := g.client.RepositoryFiles.GetFile(g.projectID, m.Raw, g.getOptions)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\n\t\tif response.StatusCode != http.StatusOK {\n\t\t\treturn nil, \"\", ErrInvalidResponse\n\t\t}\n\n\t\tcontent, err := base64.StdEncoding.DecodeString(f.Content)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\n\t\treturn io.NopCloser(strings.NewReader(string(content))), m.Identifier, nil\n\t}\n\n\treturn nil, \"\", &os.PathError{Op: fmt.Sprintf(\"read version %v\", version), Path: g.path, Err: os.ErrNotExist}\n}\n\nfunc (g *Gitlab) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {\n\tif m, ok := g.migrations.Down(version); ok {\n\t\tf, response, err := g.client.RepositoryFiles.GetFile(g.projectID, m.Raw, g.getOptions)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\n\t\tif response.StatusCode != http.StatusOK {\n\t\t\treturn nil, \"\", ErrInvalidResponse\n\t\t}\n\n\t\tcontent, err := base64.StdEncoding.DecodeString(f.Content)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\n\t\treturn io.NopCloser(strings.NewReader(string(content))), m.Identifier, nil\n\t}\n\n\treturn nil, \"\", &os.PathError{Op: fmt.Sprintf(\"read version %v\", version), Path: g.path, Err: os.ErrNotExist}\n}\n"
  },
  {
    "path": "source/gitlab/gitlab_test.go",
    "content": "package gitlab\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"testing\"\n\n\tst \"github.com/golang-migrate/migrate/v4/source/testing\"\n)\n\nvar GitlabTestSecret = \"\" // username:token\n\nfunc init() {\n\tsecrets, err := os.ReadFile(\".gitlab_test_secrets\")\n\tif err == nil {\n\t\tGitlabTestSecret = string(bytes.TrimSpace(secrets)[:])\n\t}\n}\n\nfunc Test(t *testing.T) {\n\tif len(GitlabTestSecret) == 0 {\n\t\tt.Skip(\"test requires .gitlab_test_secrets\")\n\t}\n\n\tg := &Gitlab{}\n\td, err := g.Open(\"gitlab://\" + GitlabTestSecret + \"@gitlab.com/11197284/migrations\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tst.Test(t, d)\n}\n"
  },
  {
    "path": "source/go_bindata/README.md",
    "content": "# go_bindata\n\n## Usage\n\n\n\n### Read bindata with NewWithSourceInstance\n\n```shell\ngo get -u github.com/jteeuwen/go-bindata/...\ncd examples/migrations && go-bindata -pkg migrations .\n```\n\n```go\nimport (\n  \"github.com/golang-migrate/migrate/v4\"\n  \"github.com/golang-migrate/migrate/v4/source/go_bindata\"\n  \"github.com/golang-migrate/migrate/v4/source/go_bindata/examples/migrations\"\n)\n\nfunc main() {\n  // wrap assets into Resource\n  s := bindata.Resource(migrations.AssetNames(),\n    func(name string) ([]byte, error) {\n      return migrations.Asset(name)\n    })\n    \n  d, err := bindata.WithInstance(s)\n  m, err := migrate.NewWithSourceInstance(\"go-bindata\", d, \"database://foobar\")\n  m.Up() // run your migrations and handle the errors above of course\n}\n```\n\n### Read bindata with URL (todo)\n\nThis will restore the assets in a tmp directory and then\nproxy to source/file. go-bindata must be in your `$PATH`.\n\n```\nmigrate -source go-bindata://examples/migrations/bindata.go\n```\n\n\n"
  },
  {
    "path": "source/go_bindata/examples/migrations/bindata.go",
    "content": "// Code generated by go-bindata.\n// sources:\n// 1085649617_create_users_table.down.sql\n// 1085649617_create_users_table.up.sql\n// 1185749658_add_city_to_users.down.sql\n// 1185749658_add_city_to_users.up.sql\n// DO NOT EDIT!\n\npackage testdata\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc bindataRead(data []byte, name string) ([]byte, error) {\n\tgz, err := gzip.NewReader(bytes.NewBuffer(data))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Read %q: %v\", name, err)\n\t}\n\n\tvar buf bytes.Buffer\n\t_, err = io.Copy(&buf, gz)\n\tclErr := gz.Close()\n\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Read %q: %v\", name, err)\n\t}\n\tif clErr != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buf.Bytes(), nil\n}\n\ntype asset struct {\n\tbytes []byte\n\tinfo  os.FileInfo\n}\n\ntype bindataFileInfo struct {\n\tname    string\n\tsize    int64\n\tmode    os.FileMode\n\tmodTime time.Time\n}\n\nfunc (fi bindataFileInfo) Name() string {\n\treturn fi.name\n}\nfunc (fi bindataFileInfo) Size() int64 {\n\treturn fi.size\n}\nfunc (fi bindataFileInfo) Mode() os.FileMode {\n\treturn fi.mode\n}\nfunc (fi bindataFileInfo) ModTime() time.Time {\n\treturn fi.modTime\n}\nfunc (fi bindataFileInfo) IsDir() bool {\n\treturn false\n}\nfunc (fi bindataFileInfo) Sys() interface{} {\n\treturn nil\n}\n\nvar __1085649617_create_users_tableDownSql = []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x09\\x6e\\x88\\x00\\xff\\x72\\x09\\xf2\\x0f\\x50\\x08\\x71\\x74\\xf2\\x71\\x55\\xf0\\x74\\x53\\x70\\x8d\\xf0\\x0c\\x0e\\x09\\x56\\x28\\x2d\\x4e\\x2d\\x2a\\xb6\\xe6\\x02\\x04\\x00\\x00\\xff\\xff\\x2c\\x02\\x3d\\xa7\\x1c\\x00\\x00\\x00\")\n\nfunc _1085649617_create_users_tableDownSqlBytes() ([]byte, error) {\n\treturn bindataRead(\n\t\t__1085649617_create_users_tableDownSql,\n\t\t\"1085649617_create_users_table.down.sql\",\n\t)\n}\n\nfunc _1085649617_create_users_tableDownSql() (*asset, error) {\n\tbytes, err := _1085649617_create_users_tableDownSqlBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := bindataFileInfo{name: \"1085649617_create_users_table.down.sql\", size: 28, mode: os.FileMode(420), modTime: time.Unix(1485750305, 0)}\n\ta := &asset{bytes: bytes, info: info}\n\treturn a, nil\n}\n\nvar __1085649617_create_users_tableUpSql = []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x09\\x6e\\x88\\x00\\xff\\x72\\x0e\\x72\\x75\\x0c\\x71\\x55\\x08\\x71\\x74\\xf2\\x71\\x55\\x28\\x2d\\x4e\\x2d\\x2a\\x56\\xd0\\xe0\\x52\\x00\\xb3\\xe2\\x33\\x53\\x14\\x32\\xf3\\x4a\\x52\\xd3\\x53\\x8b\\x14\\x4a\\xf3\\x32\\x0b\\x4b\\x53\\x75\\xb8\\x14\\x14\\xf2\\x12\\x73\\x53\\x15\\x14\\x14\\x14\\xca\\x12\\x8b\\x92\\x33\\x12\\x8b\\x34\\x4c\\x0c\\x34\\x41\\xc2\\xa9\\xb9\\x89\\x99\\x39\\xa8\\xc2\\x5c\\x9a\\xd6\\x5c\\x80\\x00\\x00\\x00\\xff\\xff\\xa3\\x57\\xbc\\x0b\\x5f\\x00\\x00\\x00\")\n\nfunc _1085649617_create_users_tableUpSqlBytes() ([]byte, error) {\n\treturn bindataRead(\n\t\t__1085649617_create_users_tableUpSql,\n\t\t\"1085649617_create_users_table.up.sql\",\n\t)\n}\n\nfunc _1085649617_create_users_tableUpSql() (*asset, error) {\n\tbytes, err := _1085649617_create_users_tableUpSqlBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := bindataFileInfo{name: \"1085649617_create_users_table.up.sql\", size: 95, mode: os.FileMode(420), modTime: time.Unix(1485803085, 0)}\n\ta := &asset{bytes: bytes, info: info}\n\treturn a, nil\n}\n\nvar __1185749658_add_city_to_usersDownSql = []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x09\\x6e\\x88\\x00\\xff\\x72\\xf4\\x09\\x71\\x0d\\x52\\x08\\x71\\x74\\xf2\\x71\\x55\\x28\\x2d\\x4e\\x2d\\x2a\\x56\\x70\\x09\\xf2\\x0f\\x50\\x70\\xf6\\xf7\\x09\\xf5\\xf5\\x53\\xf0\\x74\\x53\\x70\\x8d\\xf0\\x0c\\x0e\\x09\\x56\\x48\\xce\\x2c\\xa9\\xb4\\xe6\\x02\\x04\\x00\\x00\\xff\\xff\\xb7\\x52\\x88\\xd7\\x2e\\x00\\x00\\x00\")\n\nfunc _1185749658_add_city_to_usersDownSqlBytes() ([]byte, error) {\n\treturn bindataRead(\n\t\t__1185749658_add_city_to_usersDownSql,\n\t\t\"1185749658_add_city_to_users.down.sql\",\n\t)\n}\n\nfunc _1185749658_add_city_to_usersDownSql() (*asset, error) {\n\tbytes, err := _1185749658_add_city_to_usersDownSqlBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := bindataFileInfo{name: \"1185749658_add_city_to_users.down.sql\", size: 46, mode: os.FileMode(420), modTime: time.Unix(1485750443, 0)}\n\ta := &asset{bytes: bytes, info: info}\n\treturn a, nil\n}\n\nvar __1185749658_add_city_to_usersUpSql = []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x09\\x6e\\x88\\x00\\xff\\x72\\xf4\\x09\\x71\\x0d\\x52\\x08\\x71\\x74\\xf2\\x71\\x55\\x28\\x2d\\x4e\\x2d\\x2a\\x56\\x70\\x74\\x71\\x51\\x70\\xf6\\xf7\\x09\\xf5\\xf5\\x53\\x48\\xce\\x2c\\xa9\\x54\\x28\\x4b\\x2c\\x4a\\xce\\x48\\x2c\\xd2\\x30\\x34\\x30\\xd0\\xb4\\xe6\\xe2\\xe2\\x02\\x04\\x00\\x00\\xff\\xff\\xa8\\x0f\\x49\\xc6\\x32\\x00\\x00\\x00\")\n\nfunc _1185749658_add_city_to_usersUpSqlBytes() ([]byte, error) {\n\treturn bindataRead(\n\t\t__1185749658_add_city_to_usersUpSql,\n\t\t\"1185749658_add_city_to_users.up.sql\",\n\t)\n}\n\nfunc _1185749658_add_city_to_usersUpSql() (*asset, error) {\n\tbytes, err := _1185749658_add_city_to_usersUpSqlBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := bindataFileInfo{name: \"1185749658_add_city_to_users.up.sql\", size: 50, mode: os.FileMode(420), modTime: time.Unix(1485843733, 0)}\n\ta := &asset{bytes: bytes, info: info}\n\treturn a, nil\n}\n\n// Asset loads and returns the asset for the given name.\n// It returns an error if the asset could not be found or\n// could not be loaded.\nfunc Asset(name string) ([]byte, error) {\n\tcannonicalName := strings.Replace(name, \"\\\\\", \"/\", -1)\n\tif f, ok := _bindata[cannonicalName]; ok {\n\t\ta, err := f()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"Asset %s can't read by error: %v\", name, err)\n\t\t}\n\t\treturn a.bytes, nil\n\t}\n\treturn nil, fmt.Errorf(\"Asset %s not found\", name)\n}\n\n// MustAsset is like Asset but panics when Asset would return an error.\n// It simplifies safe initialization of global variables.\nfunc MustAsset(name string) []byte {\n\ta, err := Asset(name)\n\tif err != nil {\n\t\tpanic(\"asset: Asset(\" + name + \"): \" + err.Error())\n\t}\n\n\treturn a\n}\n\n// AssetInfo loads and returns the asset info for the given name.\n// It returns an error if the asset could not be found or\n// could not be loaded.\nfunc AssetInfo(name string) (os.FileInfo, error) {\n\tcannonicalName := strings.Replace(name, \"\\\\\", \"/\", -1)\n\tif f, ok := _bindata[cannonicalName]; ok {\n\t\ta, err := f()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"AssetInfo %s can't read by error: %v\", name, err)\n\t\t}\n\t\treturn a.info, nil\n\t}\n\treturn nil, fmt.Errorf(\"AssetInfo %s not found\", name)\n}\n\n// AssetNames returns the names of the assets.\nfunc AssetNames() []string {\n\tnames := make([]string, 0, len(_bindata))\n\tfor name := range _bindata {\n\t\tnames = append(names, name)\n\t}\n\treturn names\n}\n\n// _bindata is a table, holding each asset generator, mapped to its name.\nvar _bindata = map[string]func() (*asset, error){\n\t\"1085649617_create_users_table.down.sql\": _1085649617_create_users_tableDownSql,\n\t\"1085649617_create_users_table.up.sql\":   _1085649617_create_users_tableUpSql,\n\t\"1185749658_add_city_to_users.down.sql\":  _1185749658_add_city_to_usersDownSql,\n\t\"1185749658_add_city_to_users.up.sql\":    _1185749658_add_city_to_usersUpSql,\n}\n\n// AssetDir returns the file names below a certain\n// directory embedded in the file by go-bindata.\n// For example if you run go-bindata on data/... and data contains the\n// following hierarchy:\n//\n//\tdata/\n//\t  foo.txt\n//\t  img/\n//\t    a.png\n//\t    b.png\n//\n// then AssetDir(\"data\") would return []string{\"foo.txt\", \"img\"}\n// AssetDir(\"data/img\") would return []string{\"a.png\", \"b.png\"}\n// AssetDir(\"foo.txt\") and AssetDir(\"notexist\") would return an error\n// AssetDir(\"\") will return []string{\"data\"}.\nfunc AssetDir(name string) ([]string, error) {\n\tnode := _bintree\n\tif len(name) != 0 {\n\t\tcannonicalName := strings.Replace(name, \"\\\\\", \"/\", -1)\n\t\tpathList := strings.Split(cannonicalName, \"/\")\n\t\tfor _, p := range pathList {\n\t\t\tnode = node.Children[p]\n\t\t\tif node == nil {\n\t\t\t\treturn nil, fmt.Errorf(\"Asset %s not found\", name)\n\t\t\t}\n\t\t}\n\t}\n\tif node.Func != nil {\n\t\treturn nil, fmt.Errorf(\"Asset %s not found\", name)\n\t}\n\trv := make([]string, 0, len(node.Children))\n\tfor childName := range node.Children {\n\t\trv = append(rv, childName)\n\t}\n\treturn rv, nil\n}\n\ntype bintree struct {\n\tFunc     func() (*asset, error)\n\tChildren map[string]*bintree\n}\n\nvar _bintree = &bintree{nil, map[string]*bintree{\n\t\"1085649617_create_users_table.down.sql\": &bintree{_1085649617_create_users_tableDownSql, map[string]*bintree{}},\n\t\"1085649617_create_users_table.up.sql\":   &bintree{_1085649617_create_users_tableUpSql, map[string]*bintree{}},\n\t\"1185749658_add_city_to_users.down.sql\":  &bintree{_1185749658_add_city_to_usersDownSql, map[string]*bintree{}},\n\t\"1185749658_add_city_to_users.up.sql\":    &bintree{_1185749658_add_city_to_usersUpSql, map[string]*bintree{}},\n}}\n\n// RestoreAsset restores an asset under the given directory\nfunc RestoreAsset(dir, name string) error {\n\tdata, err := Asset(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\tinfo, err := AssetInfo(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = os.WriteFile(_filePath(dir, name), data, info.Mode())\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// RestoreAssets restores an asset under the given directory recursively\nfunc RestoreAssets(dir, name string) error {\n\tchildren, err := AssetDir(name)\n\t// File\n\tif err != nil {\n\t\treturn RestoreAsset(dir, name)\n\t}\n\t// Dir\n\tfor _, child := range children {\n\t\terr = RestoreAssets(dir, filepath.Join(name, child))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc _filePath(dir, name string) string {\n\tcannonicalName := strings.Replace(name, \"\\\\\", \"/\", -1)\n\treturn filepath.Join(append([]string{dir}, strings.Split(cannonicalName, \"/\")...)...)\n}\n"
  },
  {
    "path": "source/go_bindata/go-bindata.go",
    "content": "package bindata\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/golang-migrate/migrate/v4/source\"\n)\n\ntype AssetFunc func(name string) ([]byte, error)\n\nfunc Resource(names []string, afn AssetFunc) *AssetSource {\n\treturn &AssetSource{\n\t\tNames:     names,\n\t\tAssetFunc: afn,\n\t}\n}\n\ntype AssetSource struct {\n\tNames     []string\n\tAssetFunc AssetFunc\n}\n\nfunc init() {\n\tsource.Register(\"go-bindata\", &Bindata{})\n}\n\ntype Bindata struct {\n\tpath        string\n\tassetSource *AssetSource\n\tmigrations  *source.Migrations\n}\n\nfunc (b *Bindata) Open(url string) (source.Driver, error) {\n\treturn nil, fmt.Errorf(\"not yet implemented\")\n}\n\nvar (\n\tErrNoAssetSource = fmt.Errorf(\"expects *AssetSource\")\n)\n\nfunc WithInstance(instance interface{}) (source.Driver, error) {\n\tif _, ok := instance.(*AssetSource); !ok {\n\t\treturn nil, ErrNoAssetSource\n\t}\n\tas := instance.(*AssetSource)\n\n\tbn := &Bindata{\n\t\tpath:        \"<go-bindata>\",\n\t\tassetSource: as,\n\t\tmigrations:  source.NewMigrations(),\n\t}\n\n\tfor _, fi := range as.Names {\n\t\tm, err := source.DefaultParse(fi)\n\t\tif err != nil {\n\t\t\tcontinue // ignore files that we can't parse\n\t\t}\n\n\t\tif !bn.migrations.Append(m) {\n\t\t\treturn nil, fmt.Errorf(\"unable to parse file %v\", fi)\n\t\t}\n\t}\n\n\treturn bn, nil\n}\n\nfunc (b *Bindata) Close() error {\n\treturn nil\n}\n\nfunc (b *Bindata) First() (version uint, err error) {\n\tif v, ok := b.migrations.First(); !ok {\n\t\treturn 0, &os.PathError{Op: \"first\", Path: b.path, Err: os.ErrNotExist}\n\t} else {\n\t\treturn v, nil\n\t}\n}\n\nfunc (b *Bindata) Prev(version uint) (prevVersion uint, err error) {\n\tif v, ok := b.migrations.Prev(version); !ok {\n\t\treturn 0, &os.PathError{Op: fmt.Sprintf(\"prev for version %v\", version), Path: b.path, Err: os.ErrNotExist}\n\t} else {\n\t\treturn v, nil\n\t}\n}\n\nfunc (b *Bindata) Next(version uint) (nextVersion uint, err error) {\n\tif v, ok := b.migrations.Next(version); !ok {\n\t\treturn 0, &os.PathError{Op: fmt.Sprintf(\"next for version %v\", version), Path: b.path, Err: os.ErrNotExist}\n\t} else {\n\t\treturn v, nil\n\t}\n}\n\nfunc (b *Bindata) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {\n\tif m, ok := b.migrations.Up(version); ok {\n\t\tbody, err := b.assetSource.AssetFunc(m.Raw)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\t\treturn io.NopCloser(bytes.NewReader(body)), m.Identifier, nil\n\t}\n\treturn nil, \"\", &os.PathError{Op: fmt.Sprintf(\"read version %v\", version), Path: b.path, Err: os.ErrNotExist}\n}\n\nfunc (b *Bindata) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {\n\tif m, ok := b.migrations.Down(version); ok {\n\t\tbody, err := b.assetSource.AssetFunc(m.Raw)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\t\treturn io.NopCloser(bytes.NewReader(body)), m.Identifier, nil\n\t}\n\treturn nil, \"\", &os.PathError{Op: fmt.Sprintf(\"read version %v\", version), Path: b.path, Err: os.ErrNotExist}\n}\n"
  },
  {
    "path": "source/go_bindata/go-bindata_test.go",
    "content": "package bindata\n\nimport (\n\t\"testing\"\n\n\t\"github.com/golang-migrate/migrate/v4/source/go_bindata/testdata\"\n\tst \"github.com/golang-migrate/migrate/v4/source/testing\"\n)\n\nfunc Test(t *testing.T) {\n\t// wrap assets into Resource first\n\ts := Resource(testdata.AssetNames(),\n\t\tfunc(name string) ([]byte, error) {\n\t\t\treturn testdata.Asset(name)\n\t\t})\n\n\td, err := WithInstance(s)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tst.Test(t, d)\n}\n\nfunc TestWithInstance(t *testing.T) {\n\t// wrap assets into Resource\n\ts := Resource(testdata.AssetNames(),\n\t\tfunc(name string) ([]byte, error) {\n\t\t\treturn testdata.Asset(name)\n\t\t})\n\n\t_, err := WithInstance(s)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestOpen(t *testing.T) {\n\tb := &Bindata{}\n\t_, err := b.Open(\"\")\n\tif err == nil {\n\t\tt.Fatal(\"expected err, because it's not implemented yet\")\n\t}\n}\n"
  },
  {
    "path": "source/go_bindata/testdata/bindata.go",
    "content": "// Code generated by go-bindata.\n// sources:\n// 1_test.down.sql\n// 1_test.up.sql\n// 3_test.up.sql\n// 4_test.down.sql\n// 4_test.up.sql\n// 5_test.down.sql\n// 7_test.down.sql\n// 7_test.up.sql\n// DO NOT EDIT!\n\npackage testdata\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc bindataRead(data []byte, name string) ([]byte, error) {\n\tgz, err := gzip.NewReader(bytes.NewBuffer(data))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Read %q: %v\", name, err)\n\t}\n\n\tvar buf bytes.Buffer\n\t_, err = io.Copy(&buf, gz)\n\tclErr := gz.Close()\n\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Read %q: %v\", name, err)\n\t}\n\tif clErr != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buf.Bytes(), nil\n}\n\ntype asset struct {\n\tbytes []byte\n\tinfo  os.FileInfo\n}\n\ntype bindataFileInfo struct {\n\tname    string\n\tsize    int64\n\tmode    os.FileMode\n\tmodTime time.Time\n}\n\nfunc (fi bindataFileInfo) Name() string {\n\treturn fi.name\n}\nfunc (fi bindataFileInfo) Size() int64 {\n\treturn fi.size\n}\nfunc (fi bindataFileInfo) Mode() os.FileMode {\n\treturn fi.mode\n}\nfunc (fi bindataFileInfo) ModTime() time.Time {\n\treturn fi.modTime\n}\nfunc (fi bindataFileInfo) IsDir() bool {\n\treturn false\n}\nfunc (fi bindataFileInfo) Sys() interface{} {\n\treturn nil\n}\n\nvar __1_testDownSql = []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x09\\x6e\\x88\\x00\\xff\\x01\\x00\\x00\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\")\n\nfunc _1_testDownSqlBytes() ([]byte, error) {\n\treturn bindataRead(\n\t\t__1_testDownSql,\n\t\t\"1_test.down.sql\",\n\t)\n}\n\nfunc _1_testDownSql() (*asset, error) {\n\tbytes, err := _1_testDownSqlBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := bindataFileInfo{name: \"1_test.down.sql\", size: 0, mode: os.FileMode(420), modTime: time.Unix(1486440324, 0)}\n\ta := &asset{bytes: bytes, info: info}\n\treturn a, nil\n}\n\nvar __1_testUpSql = []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x09\\x6e\\x88\\x00\\xff\\x01\\x00\\x00\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\")\n\nfunc _1_testUpSqlBytes() ([]byte, error) {\n\treturn bindataRead(\n\t\t__1_testUpSql,\n\t\t\"1_test.up.sql\",\n\t)\n}\n\nfunc _1_testUpSql() (*asset, error) {\n\tbytes, err := _1_testUpSqlBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := bindataFileInfo{name: \"1_test.up.sql\", size: 0, mode: os.FileMode(420), modTime: time.Unix(1486440319, 0)}\n\ta := &asset{bytes: bytes, info: info}\n\treturn a, nil\n}\n\nvar __3_testUpSql = []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x09\\x6e\\x88\\x00\\xff\\x01\\x00\\x00\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\")\n\nfunc _3_testUpSqlBytes() ([]byte, error) {\n\treturn bindataRead(\n\t\t__3_testUpSql,\n\t\t\"3_test.up.sql\",\n\t)\n}\n\nfunc _3_testUpSql() (*asset, error) {\n\tbytes, err := _3_testUpSqlBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := bindataFileInfo{name: \"3_test.up.sql\", size: 0, mode: os.FileMode(420), modTime: time.Unix(1486440331, 0)}\n\ta := &asset{bytes: bytes, info: info}\n\treturn a, nil\n}\n\nvar __4_testDownSql = []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x09\\x6e\\x88\\x00\\xff\\x01\\x00\\x00\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\")\n\nfunc _4_testDownSqlBytes() ([]byte, error) {\n\treturn bindataRead(\n\t\t__4_testDownSql,\n\t\t\"4_test.down.sql\",\n\t)\n}\n\nfunc _4_testDownSql() (*asset, error) {\n\tbytes, err := _4_testDownSqlBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := bindataFileInfo{name: \"4_test.down.sql\", size: 0, mode: os.FileMode(420), modTime: time.Unix(1486440337, 0)}\n\ta := &asset{bytes: bytes, info: info}\n\treturn a, nil\n}\n\nvar __4_testUpSql = []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x09\\x6e\\x88\\x00\\xff\\x01\\x00\\x00\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\")\n\nfunc _4_testUpSqlBytes() ([]byte, error) {\n\treturn bindataRead(\n\t\t__4_testUpSql,\n\t\t\"4_test.up.sql\",\n\t)\n}\n\nfunc _4_testUpSql() (*asset, error) {\n\tbytes, err := _4_testUpSqlBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := bindataFileInfo{name: \"4_test.up.sql\", size: 0, mode: os.FileMode(420), modTime: time.Unix(1486440335, 0)}\n\ta := &asset{bytes: bytes, info: info}\n\treturn a, nil\n}\n\nvar __5_testDownSql = []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x09\\x6e\\x88\\x00\\xff\\x01\\x00\\x00\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\")\n\nfunc _5_testDownSqlBytes() ([]byte, error) {\n\treturn bindataRead(\n\t\t__5_testDownSql,\n\t\t\"5_test.down.sql\",\n\t)\n}\n\nfunc _5_testDownSql() (*asset, error) {\n\tbytes, err := _5_testDownSqlBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := bindataFileInfo{name: \"5_test.down.sql\", size: 0, mode: os.FileMode(420), modTime: time.Unix(1486440340, 0)}\n\ta := &asset{bytes: bytes, info: info}\n\treturn a, nil\n}\n\nvar __7_testDownSql = []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x09\\x6e\\x88\\x00\\xff\\x01\\x00\\x00\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\")\n\nfunc _7_testDownSqlBytes() ([]byte, error) {\n\treturn bindataRead(\n\t\t__7_testDownSql,\n\t\t\"7_test.down.sql\",\n\t)\n}\n\nfunc _7_testDownSql() (*asset, error) {\n\tbytes, err := _7_testDownSqlBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := bindataFileInfo{name: \"7_test.down.sql\", size: 0, mode: os.FileMode(420), modTime: time.Unix(1486440343, 0)}\n\ta := &asset{bytes: bytes, info: info}\n\treturn a, nil\n}\n\nvar __7_testUpSql = []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x09\\x6e\\x88\\x00\\xff\\x01\\x00\\x00\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\")\n\nfunc _7_testUpSqlBytes() ([]byte, error) {\n\treturn bindataRead(\n\t\t__7_testUpSql,\n\t\t\"7_test.up.sql\",\n\t)\n}\n\nfunc _7_testUpSql() (*asset, error) {\n\tbytes, err := _7_testUpSqlBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := bindataFileInfo{name: \"7_test.up.sql\", size: 0, mode: os.FileMode(420), modTime: time.Unix(1486440347, 0)}\n\ta := &asset{bytes: bytes, info: info}\n\treturn a, nil\n}\n\n// Asset loads and returns the asset for the given name.\n// It returns an error if the asset could not be found or\n// could not be loaded.\nfunc Asset(name string) ([]byte, error) {\n\tcannonicalName := strings.Replace(name, \"\\\\\", \"/\", -1)\n\tif f, ok := _bindata[cannonicalName]; ok {\n\t\ta, err := f()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"Asset %s can't read by error: %v\", name, err)\n\t\t}\n\t\treturn a.bytes, nil\n\t}\n\treturn nil, fmt.Errorf(\"Asset %s not found\", name)\n}\n\n// MustAsset is like Asset but panics when Asset would return an error.\n// It simplifies safe initialization of global variables.\nfunc MustAsset(name string) []byte {\n\ta, err := Asset(name)\n\tif err != nil {\n\t\tpanic(\"asset: Asset(\" + name + \"): \" + err.Error())\n\t}\n\n\treturn a\n}\n\n// AssetInfo loads and returns the asset info for the given name.\n// It returns an error if the asset could not be found or\n// could not be loaded.\nfunc AssetInfo(name string) (os.FileInfo, error) {\n\tcannonicalName := strings.Replace(name, \"\\\\\", \"/\", -1)\n\tif f, ok := _bindata[cannonicalName]; ok {\n\t\ta, err := f()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"AssetInfo %s can't read by error: %v\", name, err)\n\t\t}\n\t\treturn a.info, nil\n\t}\n\treturn nil, fmt.Errorf(\"AssetInfo %s not found\", name)\n}\n\n// AssetNames returns the names of the assets.\nfunc AssetNames() []string {\n\tnames := make([]string, 0, len(_bindata))\n\tfor name := range _bindata {\n\t\tnames = append(names, name)\n\t}\n\treturn names\n}\n\n// _bindata is a table, holding each asset generator, mapped to its name.\nvar _bindata = map[string]func() (*asset, error){\n\t\"1_test.down.sql\": _1_testDownSql,\n\t\"1_test.up.sql\":   _1_testUpSql,\n\t\"3_test.up.sql\":   _3_testUpSql,\n\t\"4_test.down.sql\": _4_testDownSql,\n\t\"4_test.up.sql\":   _4_testUpSql,\n\t\"5_test.down.sql\": _5_testDownSql,\n\t\"7_test.down.sql\": _7_testDownSql,\n\t\"7_test.up.sql\":   _7_testUpSql,\n}\n\n// AssetDir returns the file names below a certain\n// directory embedded in the file by go-bindata.\n// For example if you run go-bindata on data/... and data contains the\n// following hierarchy:\n//     data/\n//       foo.txt\n//       img/\n//         a.png\n//         b.png\n// then AssetDir(\"data\") would return []string{\"foo.txt\", \"img\"}\n// AssetDir(\"data/img\") would return []string{\"a.png\", \"b.png\"}\n// AssetDir(\"foo.txt\") and AssetDir(\"notexist\") would return an error\n// AssetDir(\"\") will return []string{\"data\"}.\nfunc AssetDir(name string) ([]string, error) {\n\tnode := _bintree\n\tif len(name) != 0 {\n\t\tcannonicalName := strings.Replace(name, \"\\\\\", \"/\", -1)\n\t\tpathList := strings.Split(cannonicalName, \"/\")\n\t\tfor _, p := range pathList {\n\t\t\tnode = node.Children[p]\n\t\t\tif node == nil {\n\t\t\t\treturn nil, fmt.Errorf(\"Asset %s not found\", name)\n\t\t\t}\n\t\t}\n\t}\n\tif node.Func != nil {\n\t\treturn nil, fmt.Errorf(\"Asset %s not found\", name)\n\t}\n\trv := make([]string, 0, len(node.Children))\n\tfor childName := range node.Children {\n\t\trv = append(rv, childName)\n\t}\n\treturn rv, nil\n}\n\ntype bintree struct {\n\tFunc     func() (*asset, error)\n\tChildren map[string]*bintree\n}\n\nvar _bintree = &bintree{nil, map[string]*bintree{\n\t\"1_test.down.sql\": &bintree{_1_testDownSql, map[string]*bintree{}},\n\t\"1_test.up.sql\":   &bintree{_1_testUpSql, map[string]*bintree{}},\n\t\"3_test.up.sql\":   &bintree{_3_testUpSql, map[string]*bintree{}},\n\t\"4_test.down.sql\": &bintree{_4_testDownSql, map[string]*bintree{}},\n\t\"4_test.up.sql\":   &bintree{_4_testUpSql, map[string]*bintree{}},\n\t\"5_test.down.sql\": &bintree{_5_testDownSql, map[string]*bintree{}},\n\t\"7_test.down.sql\": &bintree{_7_testDownSql, map[string]*bintree{}},\n\t\"7_test.up.sql\":   &bintree{_7_testUpSql, map[string]*bintree{}},\n}}\n\n// RestoreAsset restores an asset under the given directory\nfunc RestoreAsset(dir, name string) error {\n\tdata, err := Asset(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\tinfo, err := AssetInfo(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = os.WriteFile(_filePath(dir, name), data, info.Mode())\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// RestoreAssets restores an asset under the given directory recursively\nfunc RestoreAssets(dir, name string) error {\n\tchildren, err := AssetDir(name)\n\t// File\n\tif err != nil {\n\t\treturn RestoreAsset(dir, name)\n\t}\n\t// Dir\n\tfor _, child := range children {\n\t\terr = RestoreAssets(dir, filepath.Join(name, child))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc _filePath(dir, name string) string {\n\tcannonicalName := strings.Replace(name, \"\\\\\", \"/\", -1)\n\treturn filepath.Join(append([]string{dir}, strings.Split(cannonicalName, \"/\")...)...)\n}\n"
  },
  {
    "path": "source/godoc_vfs/vfs.go",
    "content": "// Package godoc_vfs contains a driver that reads migrations from a virtual file\n// system.\n//\n// Implementations of the filesystem interface that read from zip files and\n// maps, as well as the definition of the filesystem interface can be found in\n// the golang.org/x/tools/godoc/vfs package.\npackage godoc_vfs\n\nimport (\n\t\"github.com/golang-migrate/migrate/v4/source\"\n\t\"github.com/golang-migrate/migrate/v4/source/httpfs\"\n\n\t\"golang.org/x/tools/godoc/vfs\"\n\tvfs_httpfs \"golang.org/x/tools/godoc/vfs/httpfs\"\n)\n\nfunc init() {\n\tsource.Register(\"godoc-vfs\", &VFS{})\n}\n\n// VFS is an implementation of driver that returns migrations from a virtual\n// file system.\ntype VFS struct {\n\thttpfs.PartialDriver\n\tfs   vfs.FileSystem\n\tpath string\n}\n\n// Open implements the source.Driver interface for VFS.\n//\n// Calling this function panics, instead use the WithInstance function.\n// See the package level documentation for an example.\nfunc (b *VFS) Open(url string) (source.Driver, error) {\n\tpanic(\"not implemented\")\n}\n\n// WithInstance creates a new driver from a virtual file system.\n// If a tree named searchPath exists in the virtual filesystem, WithInstance\n// searches for migration files there.\n// It defaults to \"/\".\nfunc WithInstance(fs vfs.FileSystem, searchPath string) (source.Driver, error) {\n\tif searchPath == \"\" {\n\t\tsearchPath = \"/\"\n\t}\n\n\tbn := &VFS{\n\t\tfs:   fs,\n\t\tpath: searchPath,\n\t}\n\n\tif err := bn.Init(vfs_httpfs.New(fs), searchPath); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn bn, nil\n}\n"
  },
  {
    "path": "source/godoc_vfs/vfs_example_test.go",
    "content": "package godoc_vfs_test\n\nimport (\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/source/godoc_vfs\"\n\t\"golang.org/x/tools/godoc/vfs/mapfs\"\n)\n\nfunc Example_mapfs() {\n\tfs := mapfs.New(map[string]string{\n\t\t\"1_foobar.up.sql\":   \"1 up\",\n\t\t\"1_foobar.down.sql\": \"1 down\",\n\t\t\"3_foobar.up.sql\":   \"3 up\",\n\t\t\"4_foobar.up.sql\":   \"4 up\",\n\t\t\"4_foobar.down.sql\": \"4 down\",\n\t\t\"5_foobar.down.sql\": \"5 down\",\n\t\t\"7_foobar.up.sql\":   \"7 up\",\n\t\t\"7_foobar.down.sql\": \"7 down\",\n\t})\n\n\td, err := godoc_vfs.WithInstance(fs, \"\")\n\tif err != nil {\n\t\tpanic(\"bad migrations found!\")\n\t}\n\tm, err := migrate.NewWithSourceInstance(\"godoc-vfs\", d, \"database://foobar\")\n\tif err != nil {\n\t\tpanic(\"error creating the migrations\")\n\t}\n\terr = m.Up()\n\tif err != nil {\n\t\tpanic(\"up failed\")\n\t}\n}\n"
  },
  {
    "path": "source/godoc_vfs/vfs_test.go",
    "content": "package godoc_vfs_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/golang-migrate/migrate/v4/source/godoc_vfs\"\n\tst \"github.com/golang-migrate/migrate/v4/source/testing\"\n\t\"golang.org/x/tools/godoc/vfs/mapfs\"\n)\n\nfunc TestVFS(t *testing.T) {\n\tfs := mapfs.New(map[string]string{\n\t\t\"1_foobar.up.sql\":   \"1 up\",\n\t\t\"1_foobar.down.sql\": \"1 down\",\n\t\t\"3_foobar.up.sql\":   \"3 up\",\n\t\t\"4_foobar.up.sql\":   \"4 up\",\n\t\t\"4_foobar.down.sql\": \"4 down\",\n\t\t\"5_foobar.down.sql\": \"5 down\",\n\t\t\"7_foobar.up.sql\":   \"7 up\",\n\t\t\"7_foobar.down.sql\": \"7 down\",\n\t})\n\n\td, err := godoc_vfs.WithInstance(fs, \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tst.Test(t, d)\n}\n\nfunc TestOpen(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Error(\"Expected Open to panic\")\n\t\t}\n\t}()\n\tb := &godoc_vfs.VFS{}\n\tif _, err := b.Open(\"\"); err != nil {\n\t\tt.Error(err)\n\t}\n}\n"
  },
  {
    "path": "source/google_cloud_storage/README.md",
    "content": "# Google Cloud Storage\n\n\n## Import\n\n```go\nimport (\n  _ \"github.com/golang-migrate/migrate/v4/source/google_cloud_storage\"\n )\n ```\n\n## Connection String\n\n`gcs://<bucket>/<prefix>`\n"
  },
  {
    "path": "source/google_cloud_storage/storage.go",
    "content": "package googlecloudstorage\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"github.com/golang-migrate/migrate/v4/source\"\n\t\"google.golang.org/api/iterator\"\n)\n\nfunc init() {\n\tsource.Register(\"gcs\", &gcs{})\n}\n\ntype gcs struct {\n\tbucket     *storage.BucketHandle\n\tprefix     string\n\tmigrations *source.Migrations\n}\n\nfunc (g *gcs) Open(folder string) (source.Driver, error) {\n\tu, err := url.Parse(folder)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tclient, err := storage.NewClient(context.Background())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdriver := gcs{\n\t\tbucket:     client.Bucket(u.Host),\n\t\tprefix:     strings.Trim(u.Path, \"/\") + \"/\",\n\t\tmigrations: source.NewMigrations(),\n\t}\n\terr = driver.loadMigrations()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &driver, nil\n}\n\nfunc (g *gcs) loadMigrations() error {\n\titer := g.bucket.Objects(context.Background(), &storage.Query{\n\t\tPrefix:    g.prefix,\n\t\tDelimiter: \"/\",\n\t})\n\tobject, err := iter.Next()\n\tfor ; err == nil; object, err = iter.Next() {\n\t\t_, fileName := path.Split(object.Name)\n\t\tm, parseErr := source.DefaultParse(fileName)\n\t\tif parseErr != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif !g.migrations.Append(m) {\n\t\t\treturn fmt.Errorf(\"unable to parse file %v\", object.Name)\n\t\t}\n\t}\n\tif err != iterator.Done {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (g *gcs) Close() error {\n\treturn nil\n}\n\nfunc (g *gcs) First() (uint, error) {\n\tv, ok := g.migrations.First()\n\tif !ok {\n\t\treturn 0, os.ErrNotExist\n\t}\n\treturn v, nil\n}\n\nfunc (g *gcs) Prev(version uint) (uint, error) {\n\tv, ok := g.migrations.Prev(version)\n\tif !ok {\n\t\treturn 0, os.ErrNotExist\n\t}\n\treturn v, nil\n}\n\nfunc (g *gcs) Next(version uint) (uint, error) {\n\tv, ok := g.migrations.Next(version)\n\tif !ok {\n\t\treturn 0, os.ErrNotExist\n\t}\n\treturn v, nil\n}\n\nfunc (g *gcs) ReadUp(version uint) (io.ReadCloser, string, error) {\n\tif m, ok := g.migrations.Up(version); ok {\n\t\treturn g.open(m)\n\t}\n\treturn nil, \"\", os.ErrNotExist\n}\n\nfunc (g *gcs) ReadDown(version uint) (io.ReadCloser, string, error) {\n\tif m, ok := g.migrations.Down(version); ok {\n\t\treturn g.open(m)\n\t}\n\treturn nil, \"\", os.ErrNotExist\n}\n\nfunc (g *gcs) open(m *source.Migration) (io.ReadCloser, string, error) {\n\tobjectPath := path.Join(g.prefix, m.Raw)\n\treader, err := g.bucket.Object(objectPath).NewReader(context.Background())\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\treturn reader, m.Identifier, nil\n}\n"
  },
  {
    "path": "source/google_cloud_storage/storage_test.go",
    "content": "package googlecloudstorage\n\nimport (\n\t\"testing\"\n\n\t\"github.com/fsouza/fake-gcs-server/fakestorage\"\n\t\"github.com/golang-migrate/migrate/v4/source\"\n\tst \"github.com/golang-migrate/migrate/v4/source/testing\"\n)\n\nfunc Test(t *testing.T) {\n\tserver := fakestorage.NewServer([]fakestorage.Object{\n\t\t{BucketName: \"some-bucket\", Name: \"staging/migrations/1_foobar.up.sql\", Content: []byte(\"1 up\")},\n\t\t{BucketName: \"some-bucket\", Name: \"staging/migrations/1_foobar.down.sql\", Content: []byte(\"1 down\")},\n\t\t{BucketName: \"some-bucket\", Name: \"prod/migrations/1_foobar.up.sql\", Content: []byte(\"1 up\")},\n\t\t{BucketName: \"some-bucket\", Name: \"prod/migrations/1_foobar.down.sql\", Content: []byte(\"1 down\")},\n\t\t{BucketName: \"some-bucket\", Name: \"prod/migrations/3_foobar.up.sql\", Content: []byte(\"3 up\")},\n\t\t{BucketName: \"some-bucket\", Name: \"prod/migrations/4_foobar.up.sql\", Content: []byte(\"4 up\")},\n\t\t{BucketName: \"some-bucket\", Name: \"prod/migrations/4_foobar.down.sql\", Content: []byte(\"4 down\")},\n\t\t{BucketName: \"some-bucket\", Name: \"prod/migrations/5_foobar.down.sql\", Content: []byte(\"5 down\")},\n\t\t{BucketName: \"some-bucket\", Name: \"prod/migrations/7_foobar.up.sql\", Content: []byte(\"7 up\")},\n\t\t{BucketName: \"some-bucket\", Name: \"prod/migrations/7_foobar.down.sql\", Content: []byte(\"7 down\")},\n\t\t{BucketName: \"some-bucket\", Name: \"prod/migrations/not-a-migration.txt\"},\n\t\t{BucketName: \"some-bucket\", Name: \"prod/migrations/0-random-stuff/whatever.txt\"},\n\t})\n\tdefer server.Stop()\n\tdriver := gcs{\n\t\tbucket:     server.Client().Bucket(\"some-bucket\"),\n\t\tprefix:     \"prod/migrations/\",\n\t\tmigrations: source.NewMigrations(),\n\t}\n\terr := driver.loadMigrations()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tst.Test(t, &driver)\n}\n"
  },
  {
    "path": "source/httpfs/README.md",
    "content": "# httpfs\n\n## Usage\n\nThis package could be used to create new migration source drivers that uses\n`http.FileSystem` to read migration files.\n\nStruct `httpfs.PartialDriver` partly implements the `source.Driver` interface. It has all\nthe methods except for `Open()`. Embedding this struct and adding `Open()` method\nallows users of this package to create new migration sources. Example:\n\n```go\nstruct mydriver {\n        httpfs.PartialDriver\n}\n\nfunc (d *mydriver) Open(url string) (source.Driver, error) {\n\tvar fs http.FileSystem\n\tvar path string\n\tvar ds mydriver\n\n\t// acquire fs and path from url\n\t// set-up ds if necessary\n\n\tif err := ds.Init(fs, path); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &ds, nil\n}\n```\n\nThis package also provides a simple `source.Driver` implementation that works\nwith `http.FileSystem` provided by the user of this package. It is created with\n`httpfs.New()` call.\n\nExample of using `http.Dir()` to read migrations from `sql` directory:\n\n```go\n\tsrc, err := httpfs.New(http.Dir(\"sql\"))\n\tif err != nil {\n\t\t// do something\n\t}\n\tm, err := migrate.NewWithSourceInstance(\"httpfs\", src, \"database://url\")\n\tif err != nil {\n\t\t// do something\n\t}\n        err = m.Up()\n\t...\n```\n"
  },
  {
    "path": "source/httpfs/driver.go",
    "content": "package httpfs\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\n\t\"github.com/golang-migrate/migrate/v4/source\"\n)\n\n// driver is a migration source driver for reading migrations from\n// http.FileSystem instances. It implements source.Driver interface and can be\n// used as a migration source for the main migrate library.\ntype driver struct {\n\tPartialDriver\n}\n\n// New creates a new migrate source driver from a http.FileSystem instance and a\n// relative path to migration files within the virtual FS.\nfunc New(fs http.FileSystem, path string) (source.Driver, error) {\n\tvar d driver\n\tif err := d.Init(fs, path); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &d, nil\n}\n\n// Open completes the implementetion of source.Driver interface. Other methods\n// are implemented by the embedded PartialDriver struct.\nfunc (d *driver) Open(url string) (source.Driver, error) {\n\treturn nil, errors.New(\"open() cannot be called on the httpfs passthrough driver\")\n}\n"
  },
  {
    "path": "source/httpfs/driver_test.go",
    "content": "package httpfs_test\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/golang-migrate/migrate/v4/source/httpfs\"\n\tst \"github.com/golang-migrate/migrate/v4/source/testing\"\n)\n\nfunc TestNewOK(t *testing.T) {\n\td, err := httpfs.New(http.Dir(\"testdata\"), \"sql\")\n\tif err != nil {\n\t\tt.Errorf(\"New() expected not error, got: %s\", err)\n\t}\n\tst.Test(t, d)\n}\n\nfunc TestNewErrors(t *testing.T) {\n\td, err := httpfs.New(http.Dir(\"does-not-exist\"), \"\")\n\tif err == nil {\n\t\tt.Errorf(\"New() expected to return error\")\n\t}\n\tif d != nil {\n\t\tt.Errorf(\"New() expected to return nil driver\")\n\t}\n}\n\nfunc TestOpen(t *testing.T) {\n\td, err := httpfs.New(http.Dir(\"testdata/sql\"), \"\")\n\tif err != nil {\n\t\tt.Error(\"New() expected no error\")\n\t\treturn\n\t}\n\td, err = d.Open(\"\")\n\tif d != nil {\n\t\tt.Error(\"Open() expected to return nil driver\")\n\t}\n\tif err == nil {\n\t\tt.Error(\"Open() expected to return error\")\n\t}\n}\n"
  },
  {
    "path": "source/httpfs/partial_driver.go",
    "content": "package httpfs\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"strconv\"\n\n\t\"github.com/golang-migrate/migrate/v4/source\"\n)\n\n// PartialDriver is a helper service for creating new source drivers working with\n// http.FileSystem instances. It implements all source.Driver interface methods\n// except for Open(). New driver could embed this struct and add missing Open()\n// method.\n//\n// To prepare PartialDriver for use Init() function.\ntype PartialDriver struct {\n\tmigrations *source.Migrations\n\tfs         http.FileSystem\n\tpath       string\n}\n\n// Init prepares not initialized PartialDriver instance to read migrations from a\n// http.FileSystem instance and a relative path.\nfunc (p *PartialDriver) Init(fs http.FileSystem, path string) error {\n\troot, err := fs.Open(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfiles, err := root.Readdir(0)\n\tif err != nil {\n\t\t_ = root.Close()\n\t\treturn err\n\t}\n\tif err = root.Close(); err != nil {\n\t\treturn err\n\t}\n\n\tms := source.NewMigrations()\n\tfor _, file := range files {\n\t\tif file.IsDir() {\n\t\t\tcontinue\n\t\t}\n\n\t\tm, err := source.DefaultParse(file.Name())\n\t\tif err != nil {\n\t\t\tcontinue // ignore files that we can't parse\n\t\t}\n\n\t\tif !ms.Append(m) {\n\t\t\treturn source.ErrDuplicateMigration{\n\t\t\t\tMigration: *m,\n\t\t\t\tFileInfo:  file,\n\t\t\t}\n\t\t}\n\t}\n\n\tp.fs = fs\n\tp.path = path\n\tp.migrations = ms\n\treturn nil\n}\n\n// Close is part of source.Driver interface implementation. This is a no-op.\nfunc (p *PartialDriver) Close() error {\n\treturn nil\n}\n\n// First is part of source.Driver interface implementation.\nfunc (p *PartialDriver) First() (version uint, err error) {\n\tif version, ok := p.migrations.First(); ok {\n\t\treturn version, nil\n\t}\n\treturn 0, &os.PathError{\n\t\tOp:   \"first\",\n\t\tPath: p.path,\n\t\tErr:  os.ErrNotExist,\n\t}\n}\n\n// Prev is part of source.Driver interface implementation.\nfunc (p *PartialDriver) Prev(version uint) (prevVersion uint, err error) {\n\tif version, ok := p.migrations.Prev(version); ok {\n\t\treturn version, nil\n\t}\n\treturn 0, &os.PathError{\n\t\tOp:   \"prev for version \" + strconv.FormatUint(uint64(version), 10),\n\t\tPath: p.path,\n\t\tErr:  os.ErrNotExist,\n\t}\n}\n\n// Next is part of source.Driver interface implementation.\nfunc (p *PartialDriver) Next(version uint) (nextVersion uint, err error) {\n\tif version, ok := p.migrations.Next(version); ok {\n\t\treturn version, nil\n\t}\n\treturn 0, &os.PathError{\n\t\tOp:   \"next for version \" + strconv.FormatUint(uint64(version), 10),\n\t\tPath: p.path,\n\t\tErr:  os.ErrNotExist,\n\t}\n}\n\n// ReadUp is part of source.Driver interface implementation.\nfunc (p *PartialDriver) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {\n\tif m, ok := p.migrations.Up(version); ok {\n\t\tbody, err := p.open(path.Join(p.path, m.Raw))\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\t\treturn body, m.Identifier, nil\n\t}\n\treturn nil, \"\", &os.PathError{\n\t\tOp:   \"read up for version \" + strconv.FormatUint(uint64(version), 10),\n\t\tPath: p.path,\n\t\tErr:  os.ErrNotExist,\n\t}\n}\n\n// ReadDown is part of source.Driver interface implementation.\nfunc (p *PartialDriver) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {\n\tif m, ok := p.migrations.Down(version); ok {\n\t\tbody, err := p.open(path.Join(p.path, m.Raw))\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\t\treturn body, m.Identifier, nil\n\t}\n\treturn nil, \"\", &os.PathError{\n\t\tOp:   \"read down for version \" + strconv.FormatUint(uint64(version), 10),\n\t\tPath: p.path,\n\t\tErr:  os.ErrNotExist,\n\t}\n}\n\nfunc (p *PartialDriver) open(path string) (http.File, error) {\n\tf, err := p.fs.Open(path)\n\tif err == nil {\n\t\treturn f, nil\n\t}\n\t// Some non-standard file systems may return errors that don't include the path, that\n\t// makes debugging harder.\n\tif !errors.As(err, new(*os.PathError)) {\n\t\terr = &os.PathError{\n\t\t\tOp:   \"open\",\n\t\t\tPath: path,\n\t\t\tErr:  err,\n\t\t}\n\t}\n\treturn nil, err\n}\n"
  },
  {
    "path": "source/httpfs/partial_driver_test.go",
    "content": "package httpfs_test\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/golang-migrate/migrate/v4/source\"\n\t\"github.com/golang-migrate/migrate/v4/source/httpfs\"\n\tst \"github.com/golang-migrate/migrate/v4/source/testing\"\n)\n\ntype driver struct{ httpfs.PartialDriver }\n\nfunc (d *driver) Open(url string) (source.Driver, error) { return nil, errors.New(\"X\") }\n\ntype driverExample struct {\n\thttpfs.PartialDriver\n}\n\nfunc (d *driverExample) Open(url string) (source.Driver, error) {\n\tparts := strings.Split(url, \":\")\n\tdir := parts[0]\n\tpath := \"\"\n\tif len(parts) >= 2 {\n\t\tpath = parts[1]\n\t}\n\n\tvar de driverExample\n\treturn &de, de.Init(http.Dir(dir), path)\n}\n\nfunc TestDriverExample(t *testing.T) {\n\td, err := (*driverExample)(nil).Open(\"testdata:sql\")\n\tif err != nil {\n\t\tt.Errorf(\"Open() returned error: %s\", err)\n\t}\n\tst.Test(t, d)\n}\n\nfunc TestPartialDriverInit(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tfs   http.FileSystem\n\t\tpath string\n\t\tok   bool\n\t}{\n\t\t{\n\t\t\tname: \"valid dir and empty path\",\n\t\t\tfs:   http.Dir(\"testdata/sql\"),\n\t\t\tok:   true,\n\t\t},\n\t\t{\n\t\t\tname: \"valid dir and non-empty path\",\n\t\t\tfs:   http.Dir(\"testdata\"),\n\t\t\tpath: \"sql\",\n\t\t\tok:   true,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid dir\",\n\t\t\tfs:   http.Dir(\"does-not-exist\"),\n\t\t},\n\t\t{\n\t\t\tname: \"file instead of dir\",\n\t\t\tfs:   http.Dir(\"testdata/sql/1_foobar.up.sql\"),\n\t\t},\n\t\t{\n\t\t\tname: \"dir with duplicates\",\n\t\t\tfs:   http.Dir(\"testdata/duplicates\"),\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tvar d driver\n\t\t\terr := d.Init(test.fs, test.path)\n\t\t\tif test.ok {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Init() returned error %s\", err)\n\t\t\t\t}\n\t\t\t\tst.Test(t, &d)\n\t\t\t\tif err = d.Close(); err != nil {\n\t\t\t\t\tt.Errorf(\"Init().Close() returned error %s\", err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Init() expected error but did not get one\")\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n}\n\nfunc TestFirstWithNoMigrations(t *testing.T) {\n\tvar d driver\n\tfs := http.Dir(\"testdata/no-migrations\")\n\n\tif err := d.Init(fs, \"\"); err != nil {\n\t\tt.Errorf(\"No error on Init() expected, got: %v\", err)\n\t}\n\n\tif _, err := d.First(); err == nil {\n\t\tt.Errorf(\"Expected error on First(), got: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "source/httpfs/testdata/duplicates/1_foobar.up.sql",
    "content": "1 up\n"
  },
  {
    "path": "source/httpfs/testdata/duplicates/1_foobaz.up.sql",
    "content": "1 up\n"
  },
  {
    "path": "source/httpfs/testdata/no-migrations/some-file",
    "content": ""
  },
  {
    "path": "source/httpfs/testdata/sql/1_foobar.down.sql",
    "content": "1 down\n"
  },
  {
    "path": "source/httpfs/testdata/sql/1_foobar.up.sql",
    "content": "1 up\n"
  },
  {
    "path": "source/httpfs/testdata/sql/3_foobar.up.sql",
    "content": "3 up\n"
  },
  {
    "path": "source/httpfs/testdata/sql/4_foobar.down.sql",
    "content": "4 down\n"
  },
  {
    "path": "source/httpfs/testdata/sql/4_foobar.up.sql",
    "content": "4 up\n"
  },
  {
    "path": "source/httpfs/testdata/sql/5_foobar.down.sql",
    "content": "5 down\n"
  },
  {
    "path": "source/httpfs/testdata/sql/7_foobar.down.sql",
    "content": "7 down\n"
  },
  {
    "path": "source/httpfs/testdata/sql/7_foobar.up.sql",
    "content": "7 up\n"
  },
  {
    "path": "source/httpfs/testdata/sql/other-files-are-ignored",
    "content": ""
  },
  {
    "path": "source/httpfs/testdata/sql/subdirs-are-ignored/some-file",
    "content": ""
  },
  {
    "path": "source/iofs/README.md",
    "content": "# iofs\n\nhttps://pkg.go.dev/github.com/golang-migrate/migrate/v4/source/iofs\n"
  },
  {
    "path": "source/iofs/doc.go",
    "content": "/*\nPackage iofs provides the Go 1.16+ io/fs#FS driver.\n\nIt can accept various file systems (like embed.FS, archive/zip#Reader) implementing io/fs#FS.\n\nThis driver cannot be used with Go versions 1.15 and below.\n\nAlso, Opening with a URL scheme is not supported.\n*/\npackage iofs\n"
  },
  {
    "path": "source/iofs/example_test.go",
    "content": "//go:build go1.16\n\npackage iofs_test\n\nimport (\n\t\"embed\"\n\t\"log\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t_ \"github.com/golang-migrate/migrate/v4/database/postgres\"\n\t\"github.com/golang-migrate/migrate/v4/source/iofs\"\n)\n\n//go:embed testdata/migrations/*.sql\nvar fs embed.FS\n\nfunc Example() {\n\td, err := iofs.New(fs, \"testdata/migrations\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tm, err := migrate.NewWithSourceInstance(\"iofs\", d, \"postgres://postgres@localhost/postgres?sslmode=disable\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\terr = m.Up()\n\tif err != nil {\n\t\t// ...\n\t}\n\t// ...\n}\n"
  },
  {
    "path": "source/iofs/iofs.go",
    "content": "//go:build go1.16\n\npackage iofs\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"path\"\n\t\"strconv\"\n\n\t\"github.com/golang-migrate/migrate/v4/source\"\n)\n\ntype driver struct {\n\tPartialDriver\n}\n\n// New returns a new Driver from io/fs#FS and a relative path.\nfunc New(fsys fs.FS, path string) (source.Driver, error) {\n\tvar i driver\n\tif err := i.Init(fsys, path); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to init driver with path %s: %w\", path, err)\n\t}\n\treturn &i, nil\n}\n\n// Open is part of source.Driver interface implementation.\n// Open cannot be called on the iofs passthrough driver.\nfunc (d *driver) Open(url string) (source.Driver, error) {\n\treturn nil, errors.New(\"open() cannot be called on the iofs passthrough driver\")\n}\n\n// PartialDriver is a helper service for creating new source drivers working with\n// io/fs.FS instances. It implements all source.Driver interface methods\n// except for Open(). New driver could embed this struct and add missing Open()\n// method.\n//\n// To prepare PartialDriver for use Init() function.\ntype PartialDriver struct {\n\tmigrations *source.Migrations\n\tfsys       fs.FS\n\tpath       string\n}\n\n// Init prepares not initialized IoFS instance to read migrations from a\n// io/fs#FS instance and a relative path.\nfunc (d *PartialDriver) Init(fsys fs.FS, path string) error {\n\tentries, err := fs.ReadDir(fsys, path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tms := source.NewMigrations()\n\tfor _, e := range entries {\n\t\tif e.IsDir() {\n\t\t\tcontinue\n\t\t}\n\t\tm, err := source.DefaultParse(e.Name())\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfile, err := e.Info()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !ms.Append(m) {\n\t\t\treturn source.ErrDuplicateMigration{\n\t\t\t\tMigration: *m,\n\t\t\t\tFileInfo:  file,\n\t\t\t}\n\t\t}\n\t}\n\n\td.fsys = fsys\n\td.path = path\n\td.migrations = ms\n\treturn nil\n}\n\n// Close is part of source.Driver interface implementation.\n// Closes the file system if possible.\nfunc (d *PartialDriver) Close() error {\n\tc, ok := d.fsys.(io.Closer)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn c.Close()\n}\n\n// First is part of source.Driver interface implementation.\nfunc (d *PartialDriver) First() (version uint, err error) {\n\tif version, ok := d.migrations.First(); ok {\n\t\treturn version, nil\n\t}\n\treturn 0, &fs.PathError{\n\t\tOp:   \"first\",\n\t\tPath: d.path,\n\t\tErr:  fs.ErrNotExist,\n\t}\n}\n\n// Prev is part of source.Driver interface implementation.\nfunc (d *PartialDriver) Prev(version uint) (prevVersion uint, err error) {\n\tif version, ok := d.migrations.Prev(version); ok {\n\t\treturn version, nil\n\t}\n\treturn 0, &fs.PathError{\n\t\tOp:   \"prev for version \" + strconv.FormatUint(uint64(version), 10),\n\t\tPath: d.path,\n\t\tErr:  fs.ErrNotExist,\n\t}\n}\n\n// Next is part of source.Driver interface implementation.\nfunc (d *PartialDriver) Next(version uint) (nextVersion uint, err error) {\n\tif version, ok := d.migrations.Next(version); ok {\n\t\treturn version, nil\n\t}\n\treturn 0, &fs.PathError{\n\t\tOp:   \"next for version \" + strconv.FormatUint(uint64(version), 10),\n\t\tPath: d.path,\n\t\tErr:  fs.ErrNotExist,\n\t}\n}\n\n// ReadUp is part of source.Driver interface implementation.\nfunc (d *PartialDriver) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {\n\tif m, ok := d.migrations.Up(version); ok {\n\t\tbody, err := d.open(path.Join(d.path, m.Raw))\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\t\treturn body, m.Identifier, nil\n\t}\n\treturn nil, \"\", &fs.PathError{\n\t\tOp:   \"read up for version \" + strconv.FormatUint(uint64(version), 10),\n\t\tPath: d.path,\n\t\tErr:  fs.ErrNotExist,\n\t}\n}\n\n// ReadDown is part of source.Driver interface implementation.\nfunc (d *PartialDriver) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {\n\tif m, ok := d.migrations.Down(version); ok {\n\t\tbody, err := d.open(path.Join(d.path, m.Raw))\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\t\treturn body, m.Identifier, nil\n\t}\n\treturn nil, \"\", &fs.PathError{\n\t\tOp:   \"read down for version \" + strconv.FormatUint(uint64(version), 10),\n\t\tPath: d.path,\n\t\tErr:  fs.ErrNotExist,\n\t}\n}\n\nfunc (d *PartialDriver) open(path string) (fs.File, error) {\n\tf, err := d.fsys.Open(path)\n\tif err == nil {\n\t\treturn f, nil\n\t}\n\t// Some non-standard file systems may return errors that don't include the path, that\n\t// makes debugging harder.\n\tif !errors.As(err, new(*fs.PathError)) {\n\t\terr = &fs.PathError{\n\t\t\tOp:   \"open\",\n\t\t\tPath: path,\n\t\t\tErr:  err,\n\t\t}\n\t}\n\treturn nil, err\n}\n"
  },
  {
    "path": "source/iofs/iofs_test.go",
    "content": "//go:build go1.16\n\npackage iofs_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/golang-migrate/migrate/v4/source/iofs\"\n\tst \"github.com/golang-migrate/migrate/v4/source/testing\"\n)\n\nfunc Test(t *testing.T) {\n\t// reuse the embed.FS set in example_test.go\n\td, err := iofs.New(fs, \"testdata/migrations\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tst.Test(t, d)\n}\n"
  },
  {
    "path": "source/iofs/testdata/migrations/1_foobar.down.sql",
    "content": "1 down\n"
  },
  {
    "path": "source/iofs/testdata/migrations/1_foobar.up.sql",
    "content": "1 up\n"
  },
  {
    "path": "source/iofs/testdata/migrations/3_foobar.up.sql",
    "content": "3 up\n"
  },
  {
    "path": "source/iofs/testdata/migrations/4_foobar.down.sql",
    "content": "4 down\n"
  },
  {
    "path": "source/iofs/testdata/migrations/4_foobar.up.sql",
    "content": "4 up\n"
  },
  {
    "path": "source/iofs/testdata/migrations/5_foobar.down.sql",
    "content": "5 down\n"
  },
  {
    "path": "source/iofs/testdata/migrations/7_foobar.down.sql",
    "content": "7 down\n"
  },
  {
    "path": "source/iofs/testdata/migrations/7_foobar.up.sql",
    "content": "7 up\n"
  },
  {
    "path": "source/migration.go",
    "content": "package source\n\nimport (\n\t\"sort\"\n)\n\n// Direction is either up or down.\ntype Direction string\n\nconst (\n\tDown Direction = \"down\"\n\tUp   Direction = \"up\"\n)\n\n// Migration is a helper struct for source drivers that need to\n// build the full directory tree in memory.\n// Migration is fully independent from migrate.Migration.\ntype Migration struct {\n\t// Version is the version of this migration.\n\tVersion uint\n\n\t// Identifier can be any string that helps identifying\n\t// this migration in the source.\n\tIdentifier string\n\n\t// Direction is either Up or Down.\n\tDirection Direction\n\n\t// Raw holds the raw location path to this migration in source.\n\t// ReadUp and ReadDown will use this.\n\tRaw string\n}\n\n// Migrations wraps Migration and has an internal index\n// to keep track of Migration order.\ntype Migrations struct {\n\tindex      uintSlice\n\tmigrations map[uint]map[Direction]*Migration\n}\n\nfunc NewMigrations() *Migrations {\n\treturn &Migrations{\n\t\tindex:      make(uintSlice, 0),\n\t\tmigrations: make(map[uint]map[Direction]*Migration),\n\t}\n}\n\nfunc (i *Migrations) Append(m *Migration) (ok bool) {\n\tif m == nil {\n\t\treturn false\n\t}\n\n\tif i.migrations[m.Version] == nil {\n\t\ti.migrations[m.Version] = make(map[Direction]*Migration)\n\t}\n\n\t// reject duplicate versions\n\tif _, dup := i.migrations[m.Version][m.Direction]; dup {\n\t\treturn false\n\t}\n\n\ti.migrations[m.Version][m.Direction] = m\n\ti.buildIndex()\n\n\treturn true\n}\n\nfunc (i *Migrations) buildIndex() {\n\ti.index = make(uintSlice, 0, len(i.migrations))\n\tfor version := range i.migrations {\n\t\ti.index = append(i.index, version)\n\t}\n\tsort.Slice(i.index, func(x, y int) bool {\n\t\treturn i.index[x] < i.index[y]\n\t})\n}\n\nfunc (i *Migrations) First() (version uint, ok bool) {\n\tif len(i.index) == 0 {\n\t\treturn 0, false\n\t}\n\treturn i.index[0], true\n}\n\nfunc (i *Migrations) Prev(version uint) (prevVersion uint, ok bool) {\n\tpos := i.findPos(version)\n\tif pos >= 1 && len(i.index) > pos-1 {\n\t\treturn i.index[pos-1], true\n\t}\n\treturn 0, false\n}\n\nfunc (i *Migrations) Next(version uint) (nextVersion uint, ok bool) {\n\tpos := i.findPos(version)\n\tif pos >= 0 && len(i.index) > pos+1 {\n\t\treturn i.index[pos+1], true\n\t}\n\treturn 0, false\n}\n\nfunc (i *Migrations) Up(version uint) (m *Migration, ok bool) {\n\tif _, ok := i.migrations[version]; ok {\n\t\tif mx, ok := i.migrations[version][Up]; ok {\n\t\t\treturn mx, true\n\t\t}\n\t}\n\treturn nil, false\n}\n\nfunc (i *Migrations) Down(version uint) (m *Migration, ok bool) {\n\tif _, ok := i.migrations[version]; ok {\n\t\tif mx, ok := i.migrations[version][Down]; ok {\n\t\t\treturn mx, true\n\t\t}\n\t}\n\treturn nil, false\n}\n\nfunc (i *Migrations) findPos(version uint) int {\n\tif len(i.index) > 0 {\n\t\tix := i.index.Search(version)\n\t\tif ix < len(i.index) && i.index[ix] == version {\n\t\t\treturn ix\n\t\t}\n\t}\n\treturn -1\n}\n\ntype uintSlice []uint\n\nfunc (s uintSlice) Search(x uint) int {\n\treturn sort.Search(len(s), func(i int) bool { return s[i] >= x })\n}\n"
  },
  {
    "path": "source/migration_test.go",
    "content": "package source\n\nimport (\n\t\"testing\"\n)\n\nfunc TestNewMigrations(t *testing.T) {\n\t// TODO\n}\n\nfunc TestAppend(t *testing.T) {\n\t// TODO\n}\n\nfunc TestBuildIndex(t *testing.T) {\n\t// TODO\n}\n\nfunc TestFirst(t *testing.T) {\n\t// TODO\n}\n\nfunc TestPrev(t *testing.T) {\n\t// TODO\n}\n\nfunc TestUp(t *testing.T) {\n\t// TODO\n}\n\nfunc TestDown(t *testing.T) {\n\t// TODO\n}\n\nfunc TestFindPos(t *testing.T) {\n\tm := Migrations{index: uintSlice{1, 2, 3}}\n\tif p := m.findPos(0); p != -1 {\n\t\tt.Errorf(\"expected -1, got %v\", p)\n\t}\n\tif p := m.findPos(1); p != 0 {\n\t\tt.Errorf(\"expected 0, got %v\", p)\n\t}\n\tif p := m.findPos(3); p != 2 {\n\t\tt.Errorf(\"expected 2, got %v\", p)\n\t}\n}\n"
  },
  {
    "path": "source/parse.go",
    "content": "package source\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n)\n\nvar (\n\tErrParse = fmt.Errorf(\"no match\")\n)\n\nvar (\n\tDefaultParse = Parse\n\tDefaultRegex = Regex\n)\n\n// Regex matches the following pattern:\n//\n//\t123_name.up.ext\n//\t123_name.down.ext\nvar Regex = regexp.MustCompile(`^([0-9]+)_(.*)\\.(` + string(Down) + `|` + string(Up) + `)\\.(.*)$`)\n\n// Parse returns Migration for matching Regex pattern.\nfunc Parse(raw string) (*Migration, error) {\n\tm := Regex.FindStringSubmatch(raw)\n\tif len(m) == 5 {\n\t\tversionUint64, err := strconv.ParseUint(m[1], 10, 64)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &Migration{\n\t\t\tVersion:    uint(versionUint64),\n\t\t\tIdentifier: m[2],\n\t\t\tDirection:  Direction(m[3]),\n\t\t\tRaw:        raw,\n\t\t}, nil\n\t}\n\treturn nil, ErrParse\n}\n"
  },
  {
    "path": "source/parse_test.go",
    "content": "package source\n\nimport (\n\t\"testing\"\n)\n\nfunc TestParse(t *testing.T) {\n\ttt := []struct {\n\t\tname            string\n\t\texpectErr       error\n\t\texpectMigration *Migration\n\t}{\n\t\t{\n\t\t\tname:      \"1_foobar.up.sql\",\n\t\t\texpectErr: nil,\n\t\t\texpectMigration: &Migration{\n\t\t\t\tVersion:    1,\n\t\t\t\tIdentifier: \"foobar\",\n\t\t\t\tDirection:  Up,\n\t\t\t\tRaw:        \"1_foobar.up.sql\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"1_foobar.down.sql\",\n\t\t\texpectErr: nil,\n\t\t\texpectMigration: &Migration{\n\t\t\t\tVersion:    1,\n\t\t\t\tIdentifier: \"foobar\",\n\t\t\t\tDirection:  Down,\n\t\t\t\tRaw:        \"1_foobar.down.sql\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"1_f-o_ob+ar.up.sql\",\n\t\t\texpectErr: nil,\n\t\t\texpectMigration: &Migration{\n\t\t\t\tVersion:    1,\n\t\t\t\tIdentifier: \"f-o_ob+ar\",\n\t\t\t\tDirection:  Up,\n\t\t\t\tRaw:        \"1_f-o_ob+ar.up.sql\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"1485385885_foobar.up.sql\",\n\t\t\texpectErr: nil,\n\t\t\texpectMigration: &Migration{\n\t\t\t\tVersion:    1485385885,\n\t\t\t\tIdentifier: \"foobar\",\n\t\t\t\tDirection:  Up,\n\t\t\t\tRaw:        \"1485385885_foobar.up.sql\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"20170412214116_date_foobar.up.sql\",\n\t\t\texpectErr: nil,\n\t\t\texpectMigration: &Migration{\n\t\t\t\tVersion:    20170412214116,\n\t\t\t\tIdentifier: \"date_foobar\",\n\t\t\t\tDirection:  Up,\n\t\t\t\tRaw:        \"20170412214116_date_foobar.up.sql\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:            \"-1_foobar.up.sql\",\n\t\t\texpectErr:       ErrParse,\n\t\t\texpectMigration: nil,\n\t\t},\n\t\t{\n\t\t\tname:            \"foobar.up.sql\",\n\t\t\texpectErr:       ErrParse,\n\t\t\texpectMigration: nil,\n\t\t},\n\t\t{\n\t\t\tname:            \"1.up.sql\",\n\t\t\texpectErr:       ErrParse,\n\t\t\texpectMigration: nil,\n\t\t},\n\t\t{\n\t\t\tname:            \"1_foobar.sql\",\n\t\t\texpectErr:       ErrParse,\n\t\t\texpectMigration: nil,\n\t\t},\n\t\t{\n\t\t\tname:            \"1_foobar.up\",\n\t\t\texpectErr:       ErrParse,\n\t\t\texpectMigration: nil,\n\t\t},\n\t\t{\n\t\t\tname:            \"1_foobar.down\",\n\t\t\texpectErr:       ErrParse,\n\t\t\texpectMigration: nil,\n\t\t},\n\t}\n\n\tfor i, v := range tt {\n\t\tf, err := Parse(v.name)\n\n\t\tif err != v.expectErr {\n\t\t\tt.Errorf(\"expected %v, got %v, in %v\", v.expectErr, err, i)\n\t\t}\n\n\t\tif v.expectMigration != nil && *f != *v.expectMigration {\n\t\t\tt.Errorf(\"expected %+v, got %+v, in %v\", *v.expectMigration, *f, i)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "source/pkger/README.md",
    "content": "# pkger\n```go\npackage main\n\nimport (\n\t\"errors\"\n\t\"log\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/markbates/pkger\"\n\n\t_ \"github.com/golang-migrate/migrate/v4/database/postgres\"\n\t_ \"github.com/golang-migrate/migrate/v4/source/pkger\"\n\t_ \"github.com/lib/pq\"\n)\n\nfunc main() {\n\tpkger.Include(\"/module/path/to/migrations\")\n\tm, err := migrate.New(\"pkger:///module/path/to/migrations\", \"postgres://postgres@localhost/postgres?sslmode=disable\")\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\tif err := m.Up(); errors.Is(err, migrate.ErrNoChange) {\n\t\tlog.Println(err)\n\t} else if err != nil {\n\t\tlog.Fatalln(err)\n\t}\n}\n```\n"
  },
  {
    "path": "source/pkger/pkger.go",
    "content": "package pkger\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\tstdurl \"net/url\"\n\n\t\"github.com/golang-migrate/migrate/v4/source\"\n\t\"github.com/golang-migrate/migrate/v4/source/httpfs\"\n\t\"github.com/markbates/pkger\"\n\t\"github.com/markbates/pkger/pkging\"\n)\n\nfunc init() {\n\tsource.Register(\"pkger\", &Pkger{})\n}\n\n// Pkger is a source.Driver that reads migrations from instances of\n// pkging.Pkger.\ntype Pkger struct {\n\thttpfs.PartialDriver\n}\n\n// Open implements source.Driver. The path component of url will be used as the\n// relative location of migrations. The returned driver will use the package\n// scoped pkger.Open to access migrations.  The relative root and any\n// migrations must be added to the global pkger.Pkger instance by calling\n// pkger.Apply. Refer to Pkger documentation for more information.\nfunc (p *Pkger) Open(url string) (source.Driver, error) {\n\tu, err := stdurl.Parse(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// wrap pkger to implement http.FileSystem.\n\tfs := fsFunc(func(name string) (http.File, error) {\n\t\tf, err := pkger.Open(name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn f.(http.File), nil\n\t})\n\n\tif err := p.Init(fs, u.Path); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to init driver with relative path %q: %w\", u.Path, err)\n\t}\n\n\treturn p, nil\n}\n\n// WithInstance returns a source.Driver that is backed by an instance of\n// pkging.Pkger. The relative location of migrations is indicated by path. The\n// path must exist on the pkging.Pkger instance for the driver to initialize\n// successfully.\nfunc WithInstance(instance pkging.Pkger, path string) (source.Driver, error) {\n\tif instance == nil {\n\t\treturn nil, fmt.Errorf(\"expected instance of pkging.Pkger\")\n\t}\n\n\t// wrap pkger to implement http.FileSystem.\n\tfs := fsFunc(func(name string) (http.File, error) {\n\t\tf, err := instance.Open(name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn f.(http.File), nil\n\t})\n\n\tvar p Pkger\n\n\tif err := p.Init(fs, path); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to init driver with relative path %q: %w\", path, err)\n\t}\n\n\treturn &p, nil\n}\n\ntype fsFunc func(name string) (http.File, error)\n\n// Open implements http.FileSystem.\nfunc (f fsFunc) Open(name string) (http.File, error) {\n\treturn f(name)\n}\n"
  },
  {
    "path": "source/pkger/pkger_test.go",
    "content": "package pkger\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/here\"\n\tst \"github.com/golang-migrate/migrate/v4/source/testing\"\n\t\"github.com/markbates/pkger\"\n\t\"github.com/markbates/pkger/pkging\"\n\t\"github.com/markbates/pkger/pkging/mem\"\n)\n\nfunc Test(t *testing.T) {\n\tt.Run(\"WithInstance\", func(t *testing.T) {\n\t\ti := testInstance(t)\n\n\t\tcreatePkgerFile(t, i, \"/1_foobar.up.sql\")\n\t\tcreatePkgerFile(t, i, \"/1_foobar.down.sql\")\n\t\tcreatePkgerFile(t, i, \"/3_foobar.up.sql\")\n\t\tcreatePkgerFile(t, i, \"/4_foobar.up.sql\")\n\t\tcreatePkgerFile(t, i, \"/4_foobar.down.sql\")\n\t\tcreatePkgerFile(t, i, \"/5_foobar.down.sql\")\n\t\tcreatePkgerFile(t, i, \"/7_foobar.up.sql\")\n\t\tcreatePkgerFile(t, i, \"/7_foobar.down.sql\")\n\n\t\td, err := WithInstance(i, \"/\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tst.Test(t, d)\n\t})\n\n\tt.Run(\"Open\", func(t *testing.T) {\n\t\ti := testInstance(t)\n\n\t\tcreatePkgerFile(t, i, \"/1_foobar.up.sql\")\n\t\tcreatePkgerFile(t, i, \"/1_foobar.down.sql\")\n\t\tcreatePkgerFile(t, i, \"/3_foobar.up.sql\")\n\t\tcreatePkgerFile(t, i, \"/4_foobar.up.sql\")\n\t\tcreatePkgerFile(t, i, \"/4_foobar.down.sql\")\n\t\tcreatePkgerFile(t, i, \"/5_foobar.down.sql\")\n\t\tcreatePkgerFile(t, i, \"/7_foobar.up.sql\")\n\t\tcreatePkgerFile(t, i, \"/7_foobar.down.sql\")\n\n\t\tregisterPackageLevelInstance(t, i)\n\n\t\td, err := (&Pkger{}).Open(\"pkger:///\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tst.Test(t, d)\n\t})\n\n}\n\nfunc TestWithInstance(t *testing.T) {\n\tt.Run(\"Subdir\", func(t *testing.T) {\n\t\ti := testInstance(t)\n\n\t\t// Make sure the relative root exists so that httpfs.PartialDriver can\n\t\t// initialize.\n\t\tcreatePkgerSubdir(t, i, \"/subdir\")\n\n\t\t_, err := WithInstance(i, \"/subdir\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"\")\n\t\t}\n\t})\n\n\tt.Run(\"NilInstance\", func(t *testing.T) {\n\t\t_, err := WithInstance(nil, \"\")\n\t\tif err == nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n\n\tt.Run(\"FailInit\", func(t *testing.T) {\n\t\ti := testInstance(t)\n\n\t\t_, err := WithInstance(i, \"/fail\")\n\t\tif err == nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n\n\tt.Run(\"FailWithoutMigrations\", func(t *testing.T) {\n\t\ti := testInstance(t)\n\n\t\tcreatePkgerSubdir(t, i, \"/\")\n\n\t\td, err := WithInstance(i, \"/\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif _, err := d.First(); !errors.Is(err, os.ErrNotExist) {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t})\n}\n\nfunc TestOpen(t *testing.T) {\n\n\tt.Run(\"InvalidURL\", func(t *testing.T) {\n\t\t_, err := (&Pkger{}).Open(\":///\")\n\t\tif err == nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n\n\tt.Run(\"Root\", func(t *testing.T) {\n\t\t_, err := (&Pkger{}).Open(\"pkger:///\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n\n\tt.Run(\"FailInit\", func(t *testing.T) {\n\t\t_, err := (&Pkger{}).Open(\"pkger:///subdir\")\n\t\tif err == nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n\n\ti := testInstance(t)\n\tcreatePkgerSubdir(t, i, \"/subdir\")\n\n\t// Note that this registers the instance globally so anything run after\n\t// this will have access to everything container in the registered\n\t// instance.\n\tregisterPackageLevelInstance(t, i)\n\n\tt.Run(\"Subdir\", func(t *testing.T) {\n\t\t_, err := (&Pkger{}).Open(\"pkger:///subdir\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc TestClose(t *testing.T) {\n\td, err := (&Pkger{}).Open(\"pkger:///\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := d.Close(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc registerPackageLevelInstance(t *testing.T, pkg pkging.Pkger) {\n\tif err := pkger.Apply(pkg, nil); err != nil {\n\t\tt.Fatalf(\"failed to register pkger instance: %v\\n\", err)\n\t}\n}\n\nfunc testInstance(t *testing.T) pkging.Pkger {\n\tpkg, err := inMemoryPkger()\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create an  pkging.Pkger instance: %v\\n\", err)\n\t}\n\n\treturn pkg\n}\n\nfunc createPkgerSubdir(t *testing.T, pkg pkging.Pkger, subdir string) {\n\tif err := pkg.MkdirAll(subdir, os.ModePerm); err != nil {\n\t\tt.Fatalf(\"failed to create pkger subdir %q: %v\\n\", subdir, err)\n\t}\n}\n\nfunc createPkgerFile(t *testing.T, pkg pkging.Pkger, name string) {\n\t_, err := pkg.Create(name)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create pkger file %q: %v\\n\", name, err)\n\t}\n}\n\nfunc inMemoryPkger() (*mem.Pkger, error) {\n\tinfo, err := here.New().Current()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpkg, err := mem.New(info)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pkg, nil\n}\n"
  },
  {
    "path": "source/stub/stub.go",
    "content": "package stub\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/golang-migrate/migrate/v4/source\"\n)\n\nfunc init() {\n\tsource.Register(\"stub\", &Stub{})\n}\n\ntype Config struct{}\n\n// d, _ := source.Open(\"stub://\")\n// d.(*stub.Stub).Migrations =\n\ntype Stub struct {\n\tUrl        string\n\tInstance   interface{}\n\tMigrations *source.Migrations\n\tConfig     *Config\n}\n\nfunc (s *Stub) Open(url string) (source.Driver, error) {\n\treturn &Stub{\n\t\tUrl:        url,\n\t\tMigrations: source.NewMigrations(),\n\t\tConfig:     &Config{},\n\t}, nil\n}\n\nfunc WithInstance(instance interface{}, config *Config) (source.Driver, error) {\n\treturn &Stub{\n\t\tInstance:   instance,\n\t\tMigrations: source.NewMigrations(),\n\t\tConfig:     config,\n\t}, nil\n}\n\nfunc (s *Stub) Close() error {\n\treturn nil\n}\n\nfunc (s *Stub) First() (version uint, err error) {\n\tif v, ok := s.Migrations.First(); !ok {\n\t\treturn 0, &os.PathError{Op: \"first\", Path: s.Url, Err: os.ErrNotExist} // TODO: s.Url can be empty when called with WithInstance\n\t} else {\n\t\treturn v, nil\n\t}\n}\n\nfunc (s *Stub) Prev(version uint) (prevVersion uint, err error) {\n\tif v, ok := s.Migrations.Prev(version); !ok {\n\t\treturn 0, &os.PathError{Op: fmt.Sprintf(\"prev for version %v\", version), Path: s.Url, Err: os.ErrNotExist}\n\t} else {\n\t\treturn v, nil\n\t}\n}\n\nfunc (s *Stub) Next(version uint) (nextVersion uint, err error) {\n\tif v, ok := s.Migrations.Next(version); !ok {\n\t\treturn 0, &os.PathError{Op: fmt.Sprintf(\"next for version %v\", version), Path: s.Url, Err: os.ErrNotExist}\n\t} else {\n\t\treturn v, nil\n\t}\n}\n\nfunc (s *Stub) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {\n\tif m, ok := s.Migrations.Up(version); ok {\n\t\treturn io.NopCloser(bytes.NewBufferString(m.Identifier)), fmt.Sprintf(\"%v.up.stub\", version), nil\n\t}\n\treturn nil, \"\", &os.PathError{Op: fmt.Sprintf(\"read up version %v\", version), Path: s.Url, Err: os.ErrNotExist}\n}\n\nfunc (s *Stub) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {\n\tif m, ok := s.Migrations.Down(version); ok {\n\t\treturn io.NopCloser(bytes.NewBufferString(m.Identifier)), fmt.Sprintf(\"%v.down.stub\", version), nil\n\t}\n\treturn nil, \"\", &os.PathError{Op: fmt.Sprintf(\"read down version %v\", version), Path: s.Url, Err: os.ErrNotExist}\n}\n"
  },
  {
    "path": "source/stub/stub_test.go",
    "content": "package stub\n\nimport (\n\t\"testing\"\n\n\t\"github.com/golang-migrate/migrate/v4/source\"\n\tst \"github.com/golang-migrate/migrate/v4/source/testing\"\n)\n\nfunc Test(t *testing.T) {\n\ts := &Stub{}\n\td, err := s.Open(\"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tm := source.NewMigrations()\n\tm.Append(&source.Migration{Version: 1, Direction: source.Up})\n\tm.Append(&source.Migration{Version: 1, Direction: source.Down})\n\tm.Append(&source.Migration{Version: 3, Direction: source.Up})\n\tm.Append(&source.Migration{Version: 4, Direction: source.Up})\n\tm.Append(&source.Migration{Version: 4, Direction: source.Down})\n\tm.Append(&source.Migration{Version: 5, Direction: source.Down})\n\tm.Append(&source.Migration{Version: 7, Direction: source.Up})\n\tm.Append(&source.Migration{Version: 7, Direction: source.Down})\n\n\td.(*Stub).Migrations = m\n\n\tst.Test(t, d)\n}\n"
  },
  {
    "path": "source/testing/testing.go",
    "content": "// Package testing has the source tests.\n// All source drivers must pass the Test function.\n// This lives in it's own package so it stays a test dependency.\npackage testing\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/golang-migrate/migrate/v4/source\"\n)\n\n// Test runs tests against source implementations.\n// It assumes that the driver tests has access to the following migrations:\n//\n// u = up migration, d = down migration, n = version\n//\n//\t|  1  |  -  |  3  |  4  |  5  |  -  |  7  |\n//\t| u d |  -  | u   | u d |   d |  -  | u d |\n//\n// See source/stub/stub_test.go or source/file/file_test.go for an example.\nfunc Test(t *testing.T, d source.Driver) {\n\tTestFirst(t, d)\n\tTestPrev(t, d)\n\tTestNext(t, d)\n\tTestReadUp(t, d)\n\tTestReadDown(t, d)\n}\n\nfunc TestFirst(t *testing.T, d source.Driver) {\n\tversion, err := d.First()\n\tif err != nil {\n\t\tt.Fatalf(\"First: expected err to be nil, got %v\", err)\n\t}\n\tif version != 1 {\n\t\tt.Errorf(\"First: expected 1, got %v\", version)\n\t}\n}\n\nfunc TestPrev(t *testing.T, d source.Driver) {\n\ttt := []struct {\n\t\tversion           uint\n\t\texpectErr         error\n\t\texpectPrevVersion uint\n\t}{\n\t\t{version: 0, expectErr: os.ErrNotExist},\n\t\t{version: 1, expectErr: os.ErrNotExist},\n\t\t{version: 2, expectErr: os.ErrNotExist},\n\t\t{version: 3, expectErr: nil, expectPrevVersion: 1},\n\t\t{version: 4, expectErr: nil, expectPrevVersion: 3},\n\t\t{version: 5, expectErr: nil, expectPrevVersion: 4},\n\t\t{version: 6, expectErr: os.ErrNotExist},\n\t\t{version: 7, expectErr: nil, expectPrevVersion: 5},\n\t\t{version: 8, expectErr: os.ErrNotExist},\n\t\t{version: 9, expectErr: os.ErrNotExist},\n\t}\n\n\tfor i, v := range tt {\n\t\tpv, err := d.Prev(v.version)\n\t\tif (v.expectErr == os.ErrNotExist && !errors.Is(err, os.ErrNotExist)) && v.expectErr != err {\n\t\t\tt.Errorf(\"Prev: expected %v, got %v, in %v\", v.expectErr, err, i)\n\t\t}\n\t\tif err == nil && v.expectPrevVersion != pv {\n\t\t\tt.Errorf(\"Prev: expected %v, got %v, in %v\", v.expectPrevVersion, pv, i)\n\t\t}\n\t}\n}\n\nfunc TestNext(t *testing.T, d source.Driver) {\n\ttt := []struct {\n\t\tversion           uint\n\t\texpectErr         error\n\t\texpectNextVersion uint\n\t}{\n\t\t{version: 0, expectErr: os.ErrNotExist},\n\t\t{version: 1, expectErr: nil, expectNextVersion: 3},\n\t\t{version: 2, expectErr: os.ErrNotExist},\n\t\t{version: 3, expectErr: nil, expectNextVersion: 4},\n\t\t{version: 4, expectErr: nil, expectNextVersion: 5},\n\t\t{version: 5, expectErr: nil, expectNextVersion: 7},\n\t\t{version: 6, expectErr: os.ErrNotExist},\n\t\t{version: 7, expectErr: os.ErrNotExist},\n\t\t{version: 8, expectErr: os.ErrNotExist},\n\t\t{version: 9, expectErr: os.ErrNotExist},\n\t}\n\n\tfor i, v := range tt {\n\t\tnv, err := d.Next(v.version)\n\t\tif (v.expectErr == os.ErrNotExist && !errors.Is(err, os.ErrNotExist)) && v.expectErr != err {\n\t\t\tt.Errorf(\"Next: expected %v, got %v, in %v\", v.expectErr, err, i)\n\t\t}\n\t\tif err == nil && v.expectNextVersion != nv {\n\t\t\tt.Errorf(\"Next: expected %v, got %v, in %v\", v.expectNextVersion, nv, i)\n\t\t}\n\t}\n}\n\nfunc TestReadUp(t *testing.T, d source.Driver) {\n\ttt := []struct {\n\t\tversion   uint\n\t\texpectErr error\n\t\texpectUp  bool\n\t}{\n\t\t{version: 0, expectErr: os.ErrNotExist},\n\t\t{version: 1, expectErr: nil, expectUp: true},\n\t\t{version: 2, expectErr: os.ErrNotExist},\n\t\t{version: 3, expectErr: nil, expectUp: true},\n\t\t{version: 4, expectErr: nil, expectUp: true},\n\t\t{version: 5, expectErr: os.ErrNotExist},\n\t\t{version: 6, expectErr: os.ErrNotExist},\n\t\t{version: 7, expectErr: nil, expectUp: true},\n\t\t{version: 8, expectErr: os.ErrNotExist},\n\t}\n\n\tfor i, v := range tt {\n\t\tup, identifier, err := d.ReadUp(v.version)\n\t\tif (v.expectErr == os.ErrNotExist && !errors.Is(err, os.ErrNotExist)) ||\n\t\t\t(v.expectErr != os.ErrNotExist && err != v.expectErr) {\n\t\t\tt.Errorf(\"expected %v, got %v, in %v\", v.expectErr, err, i)\n\n\t\t} else if err == nil {\n\t\t\tif len(identifier) == 0 {\n\t\t\t\tt.Errorf(\"expected identifier not to be empty, in %v\", i)\n\t\t\t}\n\n\t\t\tif v.expectUp && up == nil {\n\t\t\t\tt.Errorf(\"expected up not to be nil, in %v\", i)\n\t\t\t} else if !v.expectUp && up != nil {\n\t\t\t\tt.Errorf(\"expected up to be nil, got %v, in %v\", up, i)\n\t\t\t}\n\t\t}\n\t\tif up != nil {\n\t\t\tif err := up.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestReadDown(t *testing.T, d source.Driver) {\n\ttt := []struct {\n\t\tversion    uint\n\t\texpectErr  error\n\t\texpectDown bool\n\t}{\n\t\t{version: 0, expectErr: os.ErrNotExist},\n\t\t{version: 1, expectErr: nil, expectDown: true},\n\t\t{version: 2, expectErr: os.ErrNotExist},\n\t\t{version: 3, expectErr: os.ErrNotExist},\n\t\t{version: 4, expectErr: nil, expectDown: true},\n\t\t{version: 5, expectErr: nil, expectDown: true},\n\t\t{version: 6, expectErr: os.ErrNotExist},\n\t\t{version: 7, expectErr: nil, expectDown: true},\n\t\t{version: 8, expectErr: os.ErrNotExist},\n\t}\n\n\tfor i, v := range tt {\n\t\tdown, identifier, err := d.ReadDown(v.version)\n\t\tif (v.expectErr == os.ErrNotExist && !errors.Is(err, os.ErrNotExist)) ||\n\t\t\t(v.expectErr != os.ErrNotExist && err != v.expectErr) {\n\t\t\tt.Errorf(\"expected %v, got %v, in %v\", v.expectErr, err, i)\n\n\t\t} else if err == nil {\n\t\t\tif len(identifier) == 0 {\n\t\t\t\tt.Errorf(\"expected identifier not to be empty, in %v\", i)\n\t\t\t}\n\n\t\t\tif v.expectDown && down == nil {\n\t\t\t\tt.Errorf(\"expected down not to be nil, in %v\", i)\n\t\t\t} else if !v.expectDown && down != nil {\n\t\t\t\tt.Errorf(\"expected down to be nil, got %v, in %v\", down, i)\n\t\t\t}\n\t\t}\n\t\tif down != nil {\n\t\t\tif err := down.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "testing/docker.go",
    "content": "// Package testing is used in driver tests and should only be used by migrate tests.\n//\n// Deprecated: If you'd like to test using Docker images, use package github.com/dhui/dktest instead\npackage testing\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand/v2\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\tdockercontainer \"github.com/docker/docker/api/types/container\"\n\tdockerimage \"github.com/docker/docker/api/types/image\"\n\tdockernetwork \"github.com/docker/docker/api/types/network\"\n\tdockerclient \"github.com/docker/docker/client\"\n)\n\nfunc NewDockerContainer(t testing.TB, image string, env []string, cmd []string) (*DockerContainer, error) {\n\tc, err := dockerclient.NewClientWithOpts(\n\t\tdockerclient.FromEnv,\n\t\tdockerclient.WithAPIVersionNegotiation(),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif cmd == nil {\n\t\tcmd = make([]string, 0)\n\t}\n\n\tcontr := &DockerContainer{\n\t\tt:         t,\n\t\tclient:    c,\n\t\tImageName: image,\n\t\tENV:       env,\n\t\tCmd:       cmd,\n\t}\n\n\tif err := contr.PullImage(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := contr.Start(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn contr, nil\n}\n\n// DockerContainer implements Instance interface\ntype DockerContainer struct {\n\tt                  testing.TB\n\tclient             *dockerclient.Client\n\tImageName          string\n\tENV                []string\n\tCmd                []string\n\tContainerId        string\n\tContainerName      string\n\tContainerJSON      dockercontainer.InspectResponse\n\tcontainerInspected bool\n\tkeepForDebugging   bool\n}\n\nfunc (d *DockerContainer) PullImage() (err error) {\n\tif d == nil {\n\t\treturn errors.New(\"cannot pull image on a nil *DockerContainer\")\n\t}\n\td.t.Logf(\"Docker: Pull image %v\", d.ImageName)\n\tr, err := d.client.ImagePull(context.Background(), d.ImageName, dockerimage.PullOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif errClose := r.Close(); errClose != nil {\n\t\t\terr = errors.Join(err, errClose)\n\t\t}\n\t}()\n\n\t// read output and log relevant lines\n\tbf := bufio.NewScanner(r)\n\tfor bf.Scan() {\n\t\tvar resp dockerImagePullOutput\n\t\tif err := json.Unmarshal(bf.Bytes(), &resp); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif strings.HasPrefix(resp.Status, \"Status: \") {\n\t\t\td.t.Logf(\"Docker: %v\", resp.Status)\n\t\t}\n\t}\n\treturn bf.Err()\n}\n\nfunc (d *DockerContainer) Start() error {\n\tif d == nil {\n\t\treturn errors.New(\"cannot start a nil *DockerContainer\")\n\t}\n\n\tcontainerName := fmt.Sprintf(\"migrate_test_%s\", pseudoRandStr(10))\n\n\t// create container first\n\tresp, err := d.client.ContainerCreate(context.Background(),\n\t\t&dockercontainer.Config{\n\t\t\tImage:  d.ImageName,\n\t\t\tLabels: map[string]string{\"migrate_test\": \"true\"},\n\t\t\tEnv:    d.ENV,\n\t\t\tCmd:    d.Cmd,\n\t\t},\n\t\t&dockercontainer.HostConfig{\n\t\t\tPublishAllPorts: true,\n\t\t},\n\t\t&dockernetwork.NetworkingConfig{},\n\t\tnil,\n\t\tcontainerName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\td.ContainerId = resp.ID\n\td.ContainerName = containerName\n\n\t// then start it\n\tif err := d.client.ContainerStart(context.Background(), resp.ID, dockercontainer.StartOptions{}); err != nil {\n\t\treturn err\n\t}\n\n\td.t.Logf(\"Docker: Started container %v (%v) for image %v listening at %v:%v\", resp.ID[0:12], containerName, d.ImageName, d.Host(), d.Port())\n\tfor _, v := range resp.Warnings {\n\t\td.t.Logf(\"Docker: Warning: %v\", v)\n\t}\n\treturn nil\n}\n\nfunc (d *DockerContainer) KeepForDebugging() {\n\tif d == nil {\n\t\treturn\n\t}\n\n\td.keepForDebugging = true\n}\n\nfunc (d *DockerContainer) Remove() error {\n\tif d == nil {\n\t\treturn errors.New(\"cannot remove a nil *DockerContainer\")\n\t}\n\n\tif d.keepForDebugging {\n\t\treturn nil\n\t}\n\n\tif len(d.ContainerId) == 0 {\n\t\treturn errors.New(\"missing containerId\")\n\t}\n\tif err := d.client.ContainerRemove(context.Background(), d.ContainerId,\n\t\tdockercontainer.RemoveOptions{\n\t\t\tForce: true,\n\t\t}); err != nil {\n\t\td.t.Log(err)\n\t\treturn err\n\t}\n\td.t.Logf(\"Docker: Removed %v\", d.ContainerName)\n\treturn nil\n}\n\nfunc (d *DockerContainer) Inspect() error {\n\tif d == nil {\n\t\treturn errors.New(\"cannot inspect a nil *DockerContainer\")\n\t}\n\n\tif len(d.ContainerId) == 0 {\n\t\treturn errors.New(\"missing containerId\")\n\t}\n\tresp, err := d.client.ContainerInspect(context.Background(), d.ContainerId)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\td.ContainerJSON = resp\n\td.containerInspected = true\n\treturn nil\n}\n\nfunc (d *DockerContainer) Logs() (io.ReadCloser, error) {\n\tif d == nil {\n\t\treturn nil, errors.New(\"cannot view logs for a nil *DockerContainer\")\n\t}\n\tif len(d.ContainerId) == 0 {\n\t\treturn nil, errors.New(\"missing containerId\")\n\t}\n\n\treturn d.client.ContainerLogs(context.Background(), d.ContainerId, dockercontainer.LogsOptions{\n\t\tShowStdout: true,\n\t\tShowStderr: true,\n\t})\n}\n\nfunc (d *DockerContainer) portMapping(selectFirst bool, cPort int) (hostIP string, hostPort uint, err error) {\n\tif !d.containerInspected {\n\t\tif err := d.Inspect(); err != nil {\n\t\t\td.t.Fatal(err)\n\t\t}\n\t}\n\n\tfor port, bindings := range d.ContainerJSON.NetworkSettings.Ports {\n\t\tif !selectFirst && port.Int() != cPort {\n\t\t\t// Skip ahead until we find the port we want\n\t\t\tcontinue\n\t\t}\n\t\tif len(bindings) > 0 {\n\t\t\tbinding := bindings[0]\n\t\t\thostPortUint, err := strconv.ParseUint(binding.HostPort, 10, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", 0, err\n\t\t\t}\n\t\t\treturn bindings[0].HostIP, uint(hostPortUint), nil\n\t\t}\n\t}\n\n\tif selectFirst {\n\t\treturn \"\", 0, errors.New(\"no port binding\")\n\t} else {\n\t\treturn \"\", 0, errors.New(\"specified port not bound\")\n\t}\n}\n\nfunc (d *DockerContainer) Host() string {\n\tif d == nil {\n\t\tpanic(\"Cannot get host for a nil *DockerContainer\")\n\t}\n\thostIP, _, err := d.portMapping(true, -1)\n\tif err != nil {\n\t\td.t.Fatal(err)\n\t}\n\n\tif hostIP == \"0.0.0.0\" {\n\t\treturn \"127.0.0.1\"\n\t} else {\n\t\treturn hostIP\n\t}\n}\n\nfunc (d *DockerContainer) Port() uint {\n\tif d == nil {\n\t\tpanic(\"Cannot get port for a nil *DockerContainer\")\n\t}\n\t_, port, err := d.portMapping(true, -1)\n\tif err != nil {\n\t\td.t.Fatal(err)\n\t}\n\treturn port\n}\n\nfunc (d *DockerContainer) PortFor(cPort int) uint {\n\tif d == nil {\n\t\tpanic(\"Cannot get port for a nil *DockerContainer\")\n\t}\n\t_, port, err := d.portMapping(false, cPort)\n\tif err != nil {\n\t\td.t.Fatal(err)\n\t}\n\treturn port\n}\n\nfunc (d *DockerContainer) NetworkSettings() dockercontainer.NetworkSettings {\n\tif d == nil {\n\t\tpanic(\"Cannot get network settings for a nil *DockerContainer\")\n\t}\n\tnetSettings := d.ContainerJSON.NetworkSettings\n\treturn *netSettings\n}\n\ntype dockerImagePullOutput struct {\n\tStatus          string `json:\"status\"`\n\tProgressDetails struct {\n\t\tCurrent int `json:\"current\"`\n\t\tTotal   int `json:\"total\"`\n\t} `json:\"progressDetail\"`\n\tId       string `json:\"id\"`\n\tProgress string `json:\"progress\"`\n}\n\nfunc pseudoRandStr(n int) string {\n\tvar letterRunes = []rune(\"abcdefghijklmnopqrstuvwxyz0123456789\")\n\tb := make([]rune, n)\n\tfor i := range b {\n\t\tb[i] = letterRunes[rand.IntN(len(letterRunes))]\n\t}\n\treturn string(b)\n}\n"
  },
  {
    "path": "testing/testing.go",
    "content": "package testing\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\tdockercontainer \"github.com/docker/docker/api/types/container\"\n)\n\ntype IsReadyFunc func(Instance) bool\n\ntype TestFunc func(*testing.T, Instance)\n\ntype Version struct {\n\tImage string\n\tENV   []string\n\tCmd   []string\n}\n\nfunc ParallelTest(t *testing.T, versions []Version, readyFn IsReadyFunc, testFn TestFunc) {\n\ttimeout, err := strconv.Atoi(os.Getenv(\"MIGRATE_TEST_CONTAINER_BOOT_TIMEOUT\"))\n\tif err != nil {\n\t\ttimeout = 60 // Cassandra docker image can take ~30s to start\n\t}\n\n\tfor i, version := range versions {\n\t\tversion := version // capture range variable, see https://goo.gl/60w3p2\n\n\t\t// Only test against one version in short mode\n\t\t// TODO: order is random, maybe always pick first version instead?\n\t\tif i > 0 && testing.Short() {\n\t\t\tt.Logf(\"Skipping %v in short mode\", version)\n\n\t\t} else {\n\t\t\tt.Run(version.Image, func(t *testing.T) {\n\t\t\t\tt.Parallel()\n\n\t\t\t\t// create new container\n\t\t\t\tcontainer, err := NewDockerContainer(t, version.Image, version.ENV, version.Cmd)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"%v\\n%s\", err, containerLogs(t, container))\n\t\t\t\t}\n\n\t\t\t\t// make sure to remove container once done\n\t\t\t\tdefer func() {\n\t\t\t\t\tif err := container.Remove(); err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\t// wait until database is ready\n\t\t\t\ttick := time.NewTicker(1000 * time.Millisecond)\n\t\t\t\tdefer tick.Stop()\n\t\t\t\ttimeout := time.NewTimer(time.Duration(timeout) * time.Second)\n\t\t\t\tdefer timeout.Stop()\n\t\t\touter:\n\t\t\t\tfor {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase <-tick.C:\n\t\t\t\t\t\tif readyFn(container) {\n\t\t\t\t\t\t\tbreak outer\n\t\t\t\t\t\t}\n\n\t\t\t\t\tcase <-timeout.C:\n\t\t\t\t\t\tt.Fatalf(\"Docker: Container not ready, timeout for %v.\\n%s\", version, containerLogs(t, container))\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// we can now run the tests\n\t\t\t\ttestFn(t, container)\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc containerLogs(t *testing.T, c *DockerContainer) []byte {\n\tr, err := c.Logs()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn nil\n\t}\n\tdefer func() {\n\t\tif err := r.Close(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\tb, err := io.ReadAll(r)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn nil\n\t}\n\treturn b\n}\n\ntype Instance interface {\n\tHost() string\n\tPort() uint\n\tPortFor(int) uint\n\tNetworkSettings() dockercontainer.NetworkSettings\n\tKeepForDebugging()\n}\n"
  },
  {
    "path": "testing/testing_test.go",
    "content": "package testing\n\nimport (\n\t\"testing\"\n)\n\nfunc ExampleParallelTest() {\n\tt := &testing.T{} // Should actually be used in a Test\n\n\tvar isReady = func(i Instance) bool {\n\t\t// Return true if Instance is ready to run tests.\n\t\t// Don't block here though.\n\t\treturn true\n\t}\n\n\t// t is *testing.T coming from parent Test(t *testing.T)\n\tParallelTest(t, []Version{{Image: \"docker_image:9.6\"}}, isReady,\n\t\tfunc(t *testing.T, i Instance) {\n\t\t\t// Run your test/s ...\n\t\t\tt.Fatal(\"...\")\n\t\t})\n}\n"
  },
  {
    "path": "util.go",
    "content": "package migrate\n\nimport (\n\t\"fmt\"\n\tnurl \"net/url\"\n\t\"strings\"\n)\n\n// MultiError holds multiple errors.\n//\n// Deprecated: Use stdlib's [errors.Join] et al. instead\n// This will be removed in the v5 release.\ntype MultiError struct {\n\tErrs []error\n}\n\n// NewMultiError returns an error type holding multiple errors.\n//\n// Deprecated: Use stdlib's [errors.Join] et al. instead\n// This will be removed in the v5 release.\nfunc NewMultiError(errs ...error) MultiError {\n\tcompactErrs := make([]error, 0)\n\tfor _, e := range errs {\n\t\tif e != nil {\n\t\t\tcompactErrs = append(compactErrs, e)\n\t\t}\n\t}\n\treturn MultiError{compactErrs}\n}\n\n// Error implements error. Multiple errors are concatenated with 'and's.\nfunc (m MultiError) Error() string {\n\tvar strs = make([]string, 0)\n\tfor _, e := range m.Errs {\n\t\tif len(e.Error()) > 0 {\n\t\t\tstrs = append(strs, e.Error())\n\t\t}\n\t}\n\treturn strings.Join(strs, \" and \")\n}\n\n// suint safely converts int to uint\n// see https://goo.gl/wEcqof\n// see https://goo.gl/pai7Dr\nfunc suint(n int) uint {\n\tif n < 0 {\n\t\tpanic(fmt.Sprintf(\"suint(%v) expects input >= 0\", n))\n\t}\n\treturn uint(n)\n}\n\n// FilterCustomQuery filters all query values starting with `x-`\nfunc FilterCustomQuery(u *nurl.URL) *nurl.URL {\n\tux := *u\n\tvx := make(nurl.Values)\n\tfor k, v := range ux.Query() {\n\t\tif len(k) <= 1 || k[0:2] != \"x-\" {\n\t\t\tvx[k] = v\n\t\t}\n\t}\n\tux.RawQuery = vx.Encode()\n\treturn &ux\n}\n"
  },
  {
    "path": "util_test.go",
    "content": "package migrate\n\nimport (\n\tnurl \"net/url\"\n\t\"testing\"\n)\n\nfunc TestSuintPanicsWithNegativeInput(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Fatal(\"expected suint to panic for -1\")\n\t\t}\n\t}()\n\tsuint(-1)\n}\n\nfunc TestSuint(t *testing.T) {\n\tif u := suint(0); u != 0 {\n\t\tt.Fatalf(\"expected 0, got %v\", u)\n\t}\n}\n\nfunc TestFilterCustomQuery(t *testing.T) {\n\tn, err := nurl.Parse(\"foo://host?a=b&x-custom=foo&c=d&ok=y\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tnx := FilterCustomQuery(n).Query()\n\tif nx.Get(\"x-custom\") != \"\" {\n\t\tt.Fatalf(\"didn't expect x-custom\")\n\t}\n\tif nx.Get(\"ok\") != \"y\" {\n\t\tt.Fatalf(\"expected ok=y, got %v\", nx.Get(\"ok\"))\n\t}\n}\n"
  }
]