Repository: xo/usql
Branch: master
Commit: 0775ee420350
Files: 268
Total size: 1.1 MB
Directory structure:
gitextract_wko_uh41/
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── announce.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build.sh
├── contrib/
│ ├── adodb/
│ │ ├── adodb.sh
│ │ └── usql-config
│ ├── cassandra/
│ │ ├── podman-config
│ │ ├── test.sql
│ │ └── usql-config
│ ├── charts/
│ │ ├── area_density_stacked.vl.json
│ │ └── penguins.json
│ ├── clickhouse/
│ │ ├── podman-config
│ │ └── usql-config
│ ├── cockroach/
│ │ └── podman-config
│ ├── config.yaml
│ ├── couchbase/
│ │ ├── README.md
│ │ ├── podman-config
│ │ └── usql-config
│ ├── db2/
│ │ ├── README.md
│ │ ├── db2cli-validate.sh
│ │ ├── db2dsdriver.cfg
│ │ ├── install-dsdriver.sh
│ │ ├── podman-config
│ │ ├── test.sql
│ │ └── usql-config
│ ├── duckdb/
│ │ └── usql-config
│ ├── exasol/
│ │ ├── podman-config
│ │ └── usql-config
│ ├── firebird/
│ │ ├── podman-config
│ │ └── usql-config
│ ├── flightsql/
│ │ ├── podman-config
│ │ └── usql-config
│ ├── go-setup.sh
│ ├── godror/
│ │ ├── fix-oob-config.sh
│ │ ├── grab-instantclient.sh
│ │ └── usql-config
│ ├── h2/
│ │ └── podman-config
│ ├── hive/
│ │ ├── podman-config
│ │ └── usql-config
│ ├── ignite/
│ │ ├── README.md
│ │ ├── activate.sh
│ │ ├── podman-config
│ │ └── usql-config
│ ├── mymysql/
│ │ └── usql-config
│ ├── mysql/
│ │ ├── podman-config
│ │ ├── test.sql
│ │ └── usql-config
│ ├── oracle/
│ │ ├── init.sql
│ │ ├── podman-config
│ │ └── usql-config
│ ├── oracle-enterprise/
│ │ ├── podman-config
│ │ └── usql-config
│ ├── pgx/
│ │ └── usql-config
│ ├── podman-run.sh
│ ├── podman-stop.sh
│ ├── postgres/
│ │ ├── init.sql
│ │ ├── podman-config
│ │ ├── schema.sql
│ │ ├── test.sql
│ │ └── usql-config
│ ├── presto/
│ │ ├── podman-config
│ │ └── usql-config
│ ├── sqlite3/
│ │ ├── build-windows-icu.sh
│ │ ├── icu-i18n-mingw64.pc
│ │ ├── test.sql
│ │ └── usql-config
│ ├── sqlserver/
│ │ ├── init.sql
│ │ ├── podman-config
│ │ ├── test.sql
│ │ └── usql-config
│ ├── trino/
│ │ ├── podman-config
│ │ └── usql-config
│ ├── usql-test.sh
│ ├── usqlpass
│ ├── usqlrc
│ ├── vertica/
│ │ ├── podman-config
│ │ └── usql-config
│ └── ydb/
│ ├── podman-config
│ └── usql-config
├── drivers/
│ ├── adodb/
│ │ └── adodb.go
│ ├── athena/
│ │ └── athena.go
│ ├── avatica/
│ │ └── avatica.go
│ ├── bigquery/
│ │ └── bigquery.go
│ ├── cassandra/
│ │ └── cassandra.go
│ ├── chai/
│ │ └── chai.go
│ ├── clickhouse/
│ │ ├── clickhouse.go
│ │ ├── clickhouse_test.go
│ │ ├── reader.go
│ │ └── testdata/
│ │ └── clickhouse.sql
│ ├── completer/
│ │ ├── completer.go
│ │ └── completer_test.go
│ ├── cosmos/
│ │ └── cosmos.go
│ ├── couchbase/
│ │ └── couchbase.go
│ ├── csvq/
│ │ └── csvq.go
│ ├── databend/
│ │ └── databend.go
│ ├── databricks/
│ │ └── databricks.go
│ ├── drivers.go
│ ├── drivers_test.go
│ ├── duckdb/
│ │ └── duckdb.go
│ ├── dynamodb/
│ │ └── dynamodb.go
│ ├── errors.go
│ ├── exasol/
│ │ └── exasol.go
│ ├── firebird/
│ │ └── firebird.go
│ ├── flightsql/
│ │ └── flightsql.go
│ ├── godror/
│ │ └── godror.go
│ ├── h2/
│ │ └── h2.go
│ ├── hive/
│ │ └── hive.go
│ ├── ignite/
│ │ └── ignite.go
│ ├── impala/
│ │ └── impala.go
│ ├── maxcompute/
│ │ └── maxcompute.go
│ ├── metadata/
│ │ ├── impala/
│ │ │ └── metadata.go
│ │ ├── informationschema/
│ │ │ ├── metadata.go
│ │ │ └── metadata_test.go
│ │ ├── metadata.go
│ │ ├── metadata_test.go
│ │ ├── mysql/
│ │ │ └── metadata.go
│ │ ├── oracle/
│ │ │ └── metadata.go
│ │ ├── postgres/
│ │ │ ├── metadata.go
│ │ │ └── metadata_test.go
│ │ ├── reader.go
│ │ └── writer.go
│ ├── moderncsqlite/
│ │ └── moderncsqlite.go
│ ├── mymysql/
│ │ └── mymysql.go
│ ├── mysql/
│ │ └── mysql.go
│ ├── netezza/
│ │ └── netezza.go
│ ├── odbc/
│ │ └── odbc.go
│ ├── oracle/
│ │ ├── oracle.go
│ │ └── orshared/
│ │ └── orshared.go
│ ├── ots/
│ │ └── ots.go
│ ├── pgx/
│ │ └── pgx.go
│ ├── postgres/
│ │ └── postgres.go
│ ├── presto/
│ │ └── presto.go
│ ├── ql/
│ │ └── ql.go
│ ├── qtype.go
│ ├── ramsql/
│ │ └── ramsql.go
│ ├── sapase/
│ │ └── sapase.go
│ ├── saphana/
│ │ └── saphana.go
│ ├── snowflake/
│ │ └── snowflake.go
│ ├── spanner/
│ │ └── spanner.go
│ ├── sqlite3/
│ │ ├── sqlite3.go
│ │ └── sqshared/
│ │ ├── reader.go
│ │ ├── reader_test.go
│ │ └── sqshared.go
│ ├── sqlserver/
│ │ ├── reader.go
│ │ ├── sqlserver.go
│ │ └── sqlserver_test.go
│ ├── testdata/
│ │ ├── .gitignore
│ │ ├── csvq/
│ │ │ └── .gitignore
│ │ ├── docker/
│ │ │ └── Dockerfile
│ │ ├── gen-golden.sh
│ │ ├── mysql.descTable.expected.txt
│ │ ├── mysql.descTable.golden.txt
│ │ ├── mysql.listFuncs.expected.txt
│ │ ├── mysql.listIndexes.expected.txt
│ │ ├── mysql.listSchemas.expected.txt
│ │ ├── mysql.listSchemas.golden.txt
│ │ ├── mysql.listTables.expected.txt
│ │ ├── mysql.listTables.golden.txt
│ │ ├── pgsql.descTable.expected.txt
│ │ ├── pgsql.descTable.golden.txt
│ │ ├── pgsql.listDbs.golden.txt
│ │ ├── pgsql.listFuncs.expected.txt
│ │ ├── pgsql.listFuncs.golden.txt
│ │ ├── pgsql.listIndexes.expected.txt
│ │ ├── pgsql.listIndexes.golden.txt
│ │ ├── pgsql.listSchemas.expected.txt
│ │ ├── pgsql.listSchemas.golden.txt
│ │ ├── pgsql.listTables.expected.txt
│ │ ├── pgsql.listTables.golden.txt
│ │ ├── sqlserver.descTable.expected.txt
│ │ ├── sqlserver.listFuncs.expected.txt
│ │ ├── sqlserver.listIndexes.expected.txt
│ │ ├── sqlserver.listSchemas.expected.txt
│ │ ├── sqlserver.listTables.expected.txt
│ │ ├── trino.descTable.expected.txt
│ │ ├── trino.listSchemas.expected.txt
│ │ └── trino.listTables.expected.txt
│ ├── trino/
│ │ ├── reader.go
│ │ └── trino.go
│ ├── vertica/
│ │ └── vertica.go
│ ├── voltdb/
│ │ └── voltdb.go
│ └── ydb/
│ └── ydb.go
├── env/
│ ├── env.go
│ ├── list.go
│ └── vars.go
├── gen.go
├── go.mod
├── go.sum
├── handler/
│ └── handler.go
├── internal/
│ ├── adodb.go
│ ├── athena.go
│ ├── avatica.go
│ ├── bigquery.go
│ ├── cassandra.go
│ ├── chai.go
│ ├── clickhouse.go
│ ├── cosmos.go
│ ├── couchbase.go
│ ├── csvq.go
│ ├── databend.go
│ ├── databricks.go
│ ├── duckdb.go
│ ├── dynamodb.go
│ ├── exasol.go
│ ├── firebird.go
│ ├── flightsql.go
│ ├── godror.go
│ ├── h2.go
│ ├── hive.go
│ ├── ignite.go
│ ├── impala.go
│ ├── internal.go
│ ├── maxcompute.go
│ ├── moderncsqlite.go
│ ├── mymysql.go
│ ├── mysql.go
│ ├── netezza.go
│ ├── odbc.go
│ ├── oracle.go
│ ├── ots.go
│ ├── pgx.go
│ ├── postgres.go
│ ├── presto.go
│ ├── ql.go
│ ├── ramsql.go
│ ├── sapase.go
│ ├── saphana.go
│ ├── snowflake.go
│ ├── spanner.go
│ ├── sqlite3.go
│ ├── sqlserver.go
│ ├── trino.go
│ ├── vertica.go
│ ├── voltdb.go
│ ├── ydb.go
│ └── z.go
├── main.go
├── main_test.go
├── metacmd/
│ ├── charts/
│ │ └── charts.go
│ ├── cmds.go
│ ├── descs.go
│ └── metacmd.go
├── rline/
│ └── rline.go
├── run.go
├── stmt/
│ ├── params.go
│ ├── params_test.go
│ ├── parse.go
│ ├── parse_test.go
│ ├── stmt.go
│ └── stmt_test.go
├── styles/
│ └── styles.go
├── testcli.go
├── testdata/
│ ├── copy.sql
│ ├── inc_test.sql
│ ├── inc_test_z.sql
│ ├── numbers.sql
│ ├── quotes.sql
│ └── sub/
│ ├── inc_test2.sql
│ └── inc_test_z.sql
├── text/
│ ├── errors.go
│ ├── license.go
│ └── text.go
└── update-deps.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every weekday
interval: "daily"
================================================
FILE: .github/workflows/announce.yml
================================================
name: Announce Release
on:
release:
types: [published]
env:
VER: ${{ github.ref_name }}
AUTHOR_NAME: Kenneth Shaw
AUTHOR_EMAIL: kenshaw@gmail.com
HOMEBREW_REPO: https://kenshaw:${{ secrets.HOMEBREW_TOKEN }}@github.com/xo/homebrew-xo.git
# cribbed from https://github.com/actions/runner/issues/691
jobs:
configure:
name: Configure
runs-on: ubuntu-latest
outputs:
username: ${{ steps.get-user.outputs.username }}
steps:
- id: get-user
name: Get User
run: echo "username=$(id -n -u)" >> $GITHUB_OUTPUT
bump-aur-package:
name: Bump AUR Package
runs-on: ubuntu-latest
needs: configure
container:
image: docker.io/library/archlinux:latest
strategy:
fail-fast: false
matrix:
include:
- package: usql
repo: aur@aur.archlinux.org:usql.git
- package: usql-bin
repo: aur@aur.archlinux.org:usql-bin.git
steps:
- name: Setup
run: |
export USERNAME=${{ needs.configure.outputs.username }}
pacman-key --init
pacman -Sy --noconfirm archlinux-keyring
pacman -Sy --noconfirm git base-devel sudo pacman-contrib devtools
useradd -m $USERNAME
echo "${USERNAME} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
- name: Add AUR SSH key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.AUR_SSH_KEY }}
name: id_ed25519
known_hosts: |
aur.archlinux.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEuBKrPzbawxA/k2g6NcyV5jmqwJ2s+zpgZGZ7tpLIcN
aur.archlinux.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDKF9vAFWdgm9Bi8uc+tYRBmXASBb5cB5iZsB7LOWWFeBrLp3r14w0/9S2vozjgqY5sJLDPONWoTTaVTbhe3vwO8CBKZTEt1AcWxuXNlRnk9FliR1/eNB9uz/7y1R0+c1Md+P98AJJSJWKN12nqIDIhjl2S1vOUvm7FNY43fU2knIhEbHybhwWeg+0wxpKwcAd/JeL5i92Uv03MYftOToUijd1pqyVFdJvQFhqD4v3M157jxS5FTOBrccAEjT+zYmFyD8WvKUa9vUclRddNllmBJdy4NyLB8SvVZULUPrP3QOlmzemeKracTlVOUG1wsDbxknF1BwSCU7CmU6UFP90kpWIyz66bP0bl67QAvlIc52Yix7pKJPbw85+zykvnfl2mdROsaT8p8R9nwCdFsBc9IiD0NhPEHcyHRwB8fokXTajk2QnGhL+zP5KnkmXnyQYOCUYo3EKMXIlVOVbPDgRYYT/XqvBuzq5S9rrU70KoI/S5lDnFfx/+lPLdtcnnEPk=
aur.archlinux.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLMiLrP8pVi5BFX2i3vepSUnpedeiewE5XptnUnau+ZoeUOPkpoCgZZuYfpaIQfhhJJI5qgnjJmr4hyJbe/zxow=
- name: Bump AUR Package (${{ matrix.package }})
run: |
export USERNAME=${{ needs.configure.outputs.username }}
export PACKAGE=${{ matrix.package }}
export REPO=${{ matrix.repo }}
export WORKDIR=$(mktemp -d /tmp/${PACKAGE}.XXXXXX)
export REPO_PATH=$WORKDIR/${PACKAGE}
export CHANGELOG=$(
curl \
-s \
-H 'Accept: application/vnd.github+json' \
https://api.github.com/repos/xo/usql/releases/tags/$VER \
|jq -r .body \
|sed -e 's/\\r//g' -e 's/\[VirusTotal.*//'
)
git clone $REPO $REPO_PATH
git config --global --add safe.directory $REPO_PATH
git -C $REPO_PATH config user.name "$AUTHOR_NAME"
git -C $REPO_PATH config user.email "$AUTHOR_EMAIL"
sed -i "s/pkgver=.*$/pkgver=${VER#v}/" $REPO_PATH/PKGBUILD
sed -i "s/sha256sums\\([^=]*\\)=.*$/sha256sums\\1=('SKIP')/" $REPO_PATH/PKGBUILD
sed -i "s/pkgrel=.*$/pkgrel=1/" $REPO_PATH/PKGBUILD
chown -R ${USERNAME}:${USERNAME} $WORKDIR
pushd $REPO_PATH &> /dev/null
sudo -u ${USERNAME} bash -c 'updpkgsums'
sudo -u ${USERNAME} bash -c 'makepkg --printsrcinfo > .SRCINFO'
popd &> /dev/null
git -C $REPO_PATH add PKGBUILD .SRCINFO
git -C $REPO_PATH commit -m "$(printf %b "Update usql version to ${VER}\n\n${CHANGELOG}")"
git -C $REPO_PATH show -C
git -C $REPO_PATH push origin master
bump-homebrew-formula:
name: Bump Homebrew Formula
runs-on: ubuntu-latest
steps:
- name: Bump Homebrew Formula
run: |
export WORKDIR=$(mktemp -d /tmp/homebrew-xo.XXXXXX)
export REPO_PATH=$WORKDIR/homebrew-xo
wget -O $WORKDIR/archive.tar.gz https://github.com/xo/usql/archive/${VER}.tar.gz
export SHA256SUM=$(sha256sum $WORKDIR/archive.tar.gz|awk '{print $1}')
export CHANGELOG=$(
curl \
-s \
-H 'Accept: application/vnd.github+json' \
https://api.github.com/repos/xo/usql/releases/tags/$VER \
|jq -r .body \
|sed -e 's/\\r//g' -e 's/\[VirusTotal.*//'
)
git clone $HOMEBREW_REPO $REPO_PATH
git -C $REPO_PATH config user.name "$AUTHOR_NAME"
git -C $REPO_PATH config user.email "$AUTHOR_EMAIL"
sed -i "s%url \".*$%url \"https://github.com/xo/usql/archive/${VER}.tar.gz\"%" $REPO_PATH/Formula/usql.rb
sed -i "s/sha256 \".*$/sha256 \"$SHA256SUM\"/" $REPO_PATH/Formula/usql.rb
git -C $REPO_PATH add Formula/usql.rb
git -C $REPO_PATH commit -m "$(printf %b "Update usql version to ${VER}\n\n${CHANGELOG}")"
git -C $REPO_PATH show -C
git -C $REPO_PATH push origin master
announce-discord:
name: Announce Discord
runs-on: ubuntu-latest
steps:
- name: Announce Discord
run: |
curl \
-H 'Content-Type: application/json' \
-d '{"username": "usql", "content": "> *usql ${{ github.ref_name }}* has been released!\n\nGet it here: https://github.com/xo/usql/releases/${{ github.ref_name }}"}' \
${{ secrets.DISCORD_WEBHOOK_URL }}
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on: push
env:
APP: usql
VER: ${{ github.ref_name }}
GO_VERSION: stable
jobs:
build_for_linux:
name: Build for Linux
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch: [amd64, arm, arm64]
steps:
- name: Install build dependencies
run: |
sudo apt-get -qq update
sudo apt-get install -y \
build-essential \
qemu-user \
gcc-arm-linux-gnueabihf \
g++-arm-linux-gnueabihf \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu \
libstdc++6-armhf-cross \
libstdc++6-arm64-cross \
libc6-dev-armhf-cross \
libc6-dev-arm64-cross \
file
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Build ${{ matrix.arch }}
run: |
./build.sh -v $VER -a ${{ matrix.arch }}
- name: Build ${{ matrix.arch }} (static)
if: matrix.arch != 'arm'
run: |
./build.sh -v $VER -a ${{ matrix.arch }} -s
- name: Archive artifacts
uses: actions/upload-artifact@v4
with:
name: dist-linux-${{ matrix.arch }}
path: build/linux/**/*
if-no-files-found: error
build_for_macos:
name: Build for macOS
runs-on: macos-latest
strategy:
matrix:
arch: [amd64, arm64]
steps:
- name: Install build dependencies
run: |
brew install coreutils
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Build ${{ matrix.arch }}
run: |
./build.sh -v $VER -a ${{ matrix.arch }}
- name: Archive artifacts
uses: actions/upload-artifact@v4
with:
name: dist-darwin-${{ matrix.arch }}
path: build/darwin/**/*
if-no-files-found: error
build_for_macos_universal:
name: Build for macOS (universal)
needs:
- build_for_macos
runs-on: macos-latest
steps:
- name: Install build dependencies
run: |
brew install coreutils
- name: Download artifacts
uses: actions/download-artifact@v4
- name: Build universal
run: |
if [ "$VER" = "master" ]; then
VER=0.0.0-dev
fi
export WORKDIR=$PWD/build/darwin/universal/$VER
mkdir -p $WORKDIR
gtar -jxvf dist-darwin-amd64/*/*/*.tar.bz2 -C $WORKDIR $APP
gtar -jxvf dist-darwin-amd64/*/*/*.tar.bz2 -C $WORKDIR LICENSE
mv $WORKDIR/$APP $WORKDIR/$APP-amd64
gtar -jxvf dist-darwin-arm64/*/*/*.tar.bz2 -C $WORKDIR $APP
mv $WORKDIR/$APP $WORKDIR/$APP-arm64
file $WORKDIR/$APP-{amd64,arm64}
lipo -create -output $WORKDIR/$APP $WORKDIR/$APP-amd64 $WORKDIR/$APP-arm64
chmod +x $WORKDIR/$APP
file $WORKDIR/$APP
rm $WORKDIR/$APP-{amd64,arm64}
sudo /usr/sbin/purge
gtar -C $WORKDIR -cjf $WORKDIR/$APP-${VER#v}-darwin-universal.tar.bz2 $APP LICENSE
ls -alh $WORKDIR/*
sha256sum $WORKDIR/*
- name: Archive artifacts
uses: actions/upload-artifact@v4
with:
name: dist-darwin-universal
path: build/darwin/**/*
if-no-files-found: error
build_for_windows:
name: Build for Windows
runs-on: windows-latest
steps:
- name: Install build dependencies
run: choco install zip
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Build amd64
shell: bash
run: |
./build.sh -v $VER
- name: Archive artifacts
uses: actions/upload-artifact@v4
with:
name: dist-windows
path: build/windows/**/*
if-no-files-found: error
draft_release:
name: Draft Release
needs:
- build_for_linux
- build_for_macos
- build_for_macos_universal
- build_for_windows
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
- name: Extract artifacts
run: |
mkdir /tmp/scan
mkdir scan
for i in $(find dist-* -name \*.tar.bz2); do
name=$(basename $i|cut -d- -f1)
ver=$(sed -e 's/\.tar\.bz2$//' <<< $(basename $i)|cut -d- -f2-)
echo "extracting $i ($name $ver)"
tar -C /tmp/scan -jv -f $i -x $name
hash=$(sha256sum /tmp/scan/$name|awk '{print $1}')
mv /tmp/scan/$name ./scan/$name-$ver-${hash:0:8}
done
for i in $(find dist-* -name \*.zip); do
name=$(basename $i|cut -d- -f1)
ver=$(sed -e 's/\.zip$//' <<< $(basename $i)|cut -d- -f2-)
echo "extracting $i ($name $ver)"
unzip -d /tmp/scan $i $name.exe
hash=$(sha256sum /tmp/scan/$name.exe|awk '{print $1}')
mv /tmp/scan/$name.exe ./scan/$name-$ver-${hash:0:8}.exe
done
file ./scan/*
sha256sum ./scan/*
- name: Submit to VirusTotal
id: virustotal
uses: crazy-max/ghaction-virustotal@v4
with:
vt_api_key: ${{ secrets.VIRUSTOTAL_API_KEY }}
request_rate: 4
files: |
./scan/*
- name: Generate Release Notes
id: generate_release_notes
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/v')
with:
name: ${{ env.APP }} ${{ env.VER }}
draft: true
generate_release_notes: true
files: |
dist-*/*/*/*.tar.bz2
dist-*/*/*/*.zip
- name: Add VirusTotal Info to Release Notes
if: startsWith(github.ref, 'refs/tags/v')
run: |
# github api url
url=https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/${{ steps.generate_release_notes.outputs.id }}
echo "url: $url"
# get release notes
release=$(
curl \
-s \
-L \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
$url
)
tag_name=$(jq -r .tag_name <<< "$release")
target_commitish=$(jq -r .target_commitish <<< "$release")
body=$(jq -r .body <<< "$release")
echo "tag_name: $tag_name target_commitish: $target_commitish"
# append virustotal details to release notes
nl=$'\n'
body+="$nl$nl[VirusTotal](https://www.virustotal.com) analysis:$nl"
while read -r -d, line; do
name=$(sed -e 's/^\.\/scan\/\([^=]\+\)=.*/\1/' <<< "$line")
vturl=$(sed -e 's/.*=\(https.*\)/\1/' <<< "$line")
body+="* [$name]($vturl)$nl"
done <<< "${{ steps.virustotal.outputs.analysis }},"
echo -e "body:\n$body"
# update release notes
export tag_name target_commitish body
curl \
-s \
-L \
-X PATCH \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--variable '%tag_name' \
--varable '%target_commitish' \
--variable '%body' \
--expand-data '{"tag_name": "{{tag_name:json}}", "body": "{{body:trim:json}}"}' \
$url
================================================
FILE: .github/workflows/test.yml
================================================
on: [push, pull_request]
name: Test usql
jobs:
test:
name: Build and Test usql
runs-on: ubuntu-latest
services:
cassandra:
image: docker.io/usql/cassandra:latest
ports:
- 9042:9042
postgres:
image: docker.io/usql/postgres:latest
env:
POSTGRES_PASSWORD: P4ssw0rd
ports:
- 5432:5432
mysql:
image: docker.io/library/mariadb
env:
MYSQL_ROOT_PASSWORD: P4ssw0rd
ports:
- 3306:3306
sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest
env:
ACCEPT_EULA: Y
MSSQL_PID: Express
SA_PASSWORD: Adm1nP@ssw0rd
ports:
- 1433:1433
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: stable
- name: Install Packages
run: |
sudo apt-get -qq update
sudo apt-get install -y build-essential libicu-dev unixodbc unixodbc-dev
- name: Checkout code
uses: actions/checkout@v4
- name: Unit Tests
run: |
go test -v ./stmt
- name: Build with all drivers
run: |
./build.sh -b -t all
- name: Shell Tests
run: |
go run testcli.go &> output.log
ls -alh output.log
- name: Archive output
uses: actions/upload-artifact@v4
if: always()
with:
name: output
path: output.log
if-no-files-found: error
================================================
FILE: .gitignore
================================================
/usql
/usql.exe
/build/
/coverage.out
/*.sql
/*.txt
.usql_history*
.[a-f0-9]*
*.ini
*.csv
*.db
*.zip
*.out
*.sqlite3
*.sqlite3-journal
*.duckdb
*.wal
/instantclient*
/*.pc
.vscode/
================================================
FILE: CONTRIBUTING.md
================================================
Contributing to usql
====================
Any contributions are welcome. If you found a bug, or a missing feature,
take a look at existing [issues](https://github.com/xo/usql/issues)
and create a new one if needed.
You can also open up a [pull request](https://github.com/xo/usql/pulls) (PR)
with code or documentation changes.
# Adding a new driver
1. Add a new schema in [dburl](https://github.com/xo/dburl).
1. Create a new go package in `drivers`. It should have an `init()` function, that would call `drivers.Register()`.
1. Regenerate code in the `internal` package by running `internal/gen.sh`.
1. Add any new required modules using `go get` or by editing `go.mod` manually and running `go mod tidy`.
1. Run all tests, build `usql` and see if the new driver works.
1. Update `README.md`.
> Tip: check out closed PRs for examples, and/or search the codebase
for names of databases you're familiar with.
# Enabling metadata introspection for a driver
For `\d*` commands to work, `usql` needs to know how to read the structure of a database.
A driver must provide a metadata reader, by setting the `NewMetadataReader` property
in the `drivers.Driver` structure passed to `drivers.Register()`. This needs to be a function
that given a database and reader options, returns a reader instance for this particular driver.
If the database has a `information_schema` schema, with standard tables like `tables` and `columns`,
you can use an existing reader from the `drivers/informationschema` package.
Since there are usually minor difference in objects defined in that schema in different databases,
there's a set of options to configure this reader. Refer to
the [package docs](https://pkg.go.dev/github.com/xo/usql/drivers/metadata/informationschema) for details.
If you can't use the `informationschema` reader, consider implementing a new one.
It should implement at least one of the following reader interfaces:
* CatalogReader
* SchemaReader
* TableReader
* ColumnReader
* IndexReader
* IndexColumnReader
* FunctionReader
* FunctionColumnReader
* SequenceReader
Every of these interfaces consist of a single function, that takes a `Filter` structure as an argument,
and returns a set of results and an error.
Example drivers using their own readers include:
* `sqlite3`
* `oracle` and `godror` sharing the same reader
If you want to use the `informationschema` reader, but need to override one or more readers,
use the `metadata.NewPluginReader(readers ...Reader)` function. It returns an object calling
reader functions from the last reader passed in the arguments, that implements it.
Example drivers extending an `informationschema` reader using a plugin reader:
* `postgres`
`\d*` commands are actually implemented by a metadata writer. There's currently only one,
but it too can be replaced and/or extended.
# Enabling autocomplete for a driver
If a driver provides a metadata reader, the default completer will use it.
A driver can provide it's own completer, by setting the `NewCompleter` property
in the `drivers.Driver` structure passed to `drivers.Register()`.
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015-2025 Kenneth Shaw
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
`usql` is a universal command-line interface for PostgreSQL, MySQL, Oracle
Database, SQLite3, Microsoft SQL Server, [and many other databases][databases]
including NoSQL and non-relational databases!
`usql` provides a simple way to work with [SQL and NoSQL databases][databases]
via a command-line inspired by PostgreSQL's `psql`. `usql` supports most of the
core `psql` features, such as [variables][variables], [backticks][backticks],
[backslash commands][commands] and has additional features that `psql` does
not, such as [multiple database support][databases], [copying between databases][copying],
[syntax highlighting][highlighting], [context-based completion][completion],
and [terminal graphics][termgraphics].
Database administrators and developers that would prefer to work with a tool
like `psql` with non-PostgreSQL databases, will find `usql` intuitive,
easy-to-use, and a great replacement for the command-line clients/tools
for other databases.
[![Unit Tests][usql-ci-status]][usql-ci]
[![Go Reference][goref-usql-status]][goref-usql]
[![Releases][release-status]][Releases]
[![Discord Discussion][discord-status]][discord]
[usql-ci]: https://github.com/xo/usql/actions/workflows/test.yml "Test CI"
[usql-ci-status]: https://github.com/xo/usql/actions/workflows/test.yml/badge.svg "Test CI"
[goref-usql]: https://pkg.go.dev/github.com/xo/usql "Go Reference"
[goref-usql-status]: https://pkg.go.dev/badge/github.com/xo/usql.svg "Go Reference"
[release-status]: https://img.shields.io/github/v/release/xo/usql?display_name=tag&sort=semver "Latest Release"
[discord]: https://discord.gg/WDWAgXwJqN "Discord Discussion"
[discord-status]: https://img.shields.io/discord/829150509658013727.svg?label=Discord&logo=Discord&colorB=7289da&style=flat-square "Discord Discussion"
[installing]: #installing "Installing"
[databases]: #database-support "Database Support"
[releases]: https://github.com/xo/usql/releases "Releases"
## Installing
`usql` can be installed [via Release][], [via Homebrew][], [via AUR][], [via
Scoop][], [via Go][], or [via Docker][]:
[via Release]: #installing-via-release
[via Homebrew]: #installing-via-homebrew-macos-and-linux
[via AUR]: #installing-via-aur-arch-linux
[via Scoop]: #installing-via-scoop-windows
[via Go]: #installing-via-go
[via Docker]: #installing-via-docker
### Installing via Release
1. [Download a release for your platform][releases]
2. Extract the `usql` or `usql.exe` file from the `.tar.bz2` or `.zip` file
3. Move the extracted executable to somewhere on your `$PATH` (Linux/macOS) or
`%PATH%` (Windows)
### Installing via Homebrew (macOS and Linux)
Install `usql` from the [`xo/xo` tap][xo-tap] in the usual way with the [`brew`
command][homebrew]:
```sh
# install usql with most drivers
$ brew install xo/xo/usql
```
Support for [ODBC databases][databases] is available through the `--with-odbc`
install flag:
```sh
# add xo tap
$ brew tap xo/xo
# install usql with odbc support
$ brew install --with-odbc usql
```
### Installing via AUR (Arch Linux)
Install `usql` from the [Arch Linux AUR][aur] in the usual way with the [`yay`
command][yay]:
```sh
# install usql with most drivers
$ yay -S usql
```
Alternately, build and [install using `makepkg`][arch-makepkg]:
```sh
$ git clone https://aur.archlinux.org/usql.git && cd usql
$ makepkg -si
==> Making package: usql 0.12.10-1 (Fri 26 Aug 2022 05:56:09 AM WIB)
==> Checking runtime dependencies...
==> Checking buildtime dependencies...
==> Retrieving sources...
-> Downloading usql-0.12.10.tar.gz...
...
```
### Installing via Scoop (Windows)
Install `usql` using [Scoop](https://scoop.sh):
```powershell
# Optional: Needed to run a remote script the first time
> Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
# install scoop if not already installed
> irm get.scoop.sh | iex
# install usql with scoop
> scoop install usql
```
### Installing via Go
Install `usql` in the usual Go fashion:
```sh
# install latest usql version with base drivers
$ go install github.com/xo/usql@latest
# alternately, install usql with most drivers (see below for info about build tags)
$ go install -tags most github.com/xo/usql@latest
```
See [below for information](#building) on `usql` build tags.
### Installing via Docker
An [official container image (`docker.io/usql/usql`)][docker-hub] is maintained
by the `usql` team, and can be used with Docker, Podman, or other container
runtime.
[docker-hub]: https://hub.docker.com/r/usql/usql
Install `usql` with Docker, Podman, or other container runtime:
```sh
# run interactive shell and mount the $PWD/data directory as a volume for use
# within the container
$ docker run --rm -it --volume $(pwd)/data:/data docker.io/usql/usql:latest sqlite3://data/test.db
Trying to pull docker.io/usql/usql:latest...
Getting image source signatures
Copying blob af48168d69d8 done |
Copying blob efc2b5ad9eec skipped: already exists
Copying config 917ceb411d done |
Writing manifest to image destination
Connected with driver sqlite3 (SQLite3 3.45.1)
Type "help" for help.
sq:data/test.db=> \q
# run postgres locally
$ docker run --detach --rm --name=postgres --publish=5432:5432 --env=POSTGRES_PASSWORD=P4ssw0rd docker.io/usql/postgres
# connect to local postgres instance
$ docker run --rm --network host -it docker.io/usql/usql:latest postgres://postgres:P4ssw0rd@localhost
Connected with driver postgres (PostgreSQL 16.3 (Debian 16.3-1.pgdg120+1))
Type "help" for help.
pg:postgres@localhost=> \q
# run specific usql version
$ docker run --rm -it docker.io/usql/usql:0.19.3
```
## Building
When building `usql` out-of-the-box with `go build` or `go install`, only the
[`base` drivers][databases] for PostgreSQL, MySQL, SQLite3, Microsoft SQL
Server, Oracle, CSVQ will be included in the build:
```sh
# build/install with base drivers (PostgreSQL, MySQL, SQLite3, Microsoft SQL Server,
# Oracle, CSVQ)
$ go install github.com/xo/usql@master
```
Other databases can be enabled by specifying the [build tag for their database
driver][databases].
```sh
# build/install with base, Avatica, and ODBC drivers
$ go install -tags 'avatica odbc' github.com/xo/usql@master
```
For every build tag ``, there is also a `no_` build tag
that will disable the driver:
```sh
# build/install most drivers, excluding Avatica, Couchbase, and PostgreSQL
$ go install -tags 'most no_avatica no_couchbase no_postgres' github.com/xo/usql@master
```
By specifying the build tags `most` or `all`, the build will include most, and
all SQL drivers, respectively:
```sh
# build/install with most drivers (excludes CGO drivers and problematic drivers)
$ go install -tags most github.com/xo/usql@master
# build/install all drivers (includes CGO drivers and problematic drivers)
$ go install -tags all github.com/xo/usql@master
```
## Database Support
`usql` works with all Go standard library compatible SQL drivers supported by
[`github.com/xo/dburl`][dburl].
The list of drivers that `usql` was built with can be displayed with the
[`\drivers` command][commands]:
```sh
$ cd $GOPATH/src/github.com/xo/usql
# build excluding the base drivers, and including cassandra and moderncsqlite
$ go build -tags 'no_postgres no_oracle no_sqlserver no_sqlite3 cassandra moderncsqlite'
# show built driver support
$ ./usql -c '\drivers'
Available Drivers:
cql [ca, scy, scylla, datastax, cassandra]
memsql (mysql) [me]
moderncsqlite [mq, sq, file, sqlite, sqlite3, modernsqlite]
mysql [my, maria, aurora, mariadb, percona]
tidb (mysql) [ti]
vitess (mysql) [vt]
```
The above shows that `usql` was built with only the `mysql`, `cassandra` (ie,
`cql`), and `moderncsqlite` drivers. The output above reflects information
about the drivers available to `usql`, specifically the internal driver name,
its primary URL scheme, the driver's available scheme aliases (shown in
`[...]`), and the real/underlying driver (shown in `(...)`) for wire compatible
drivers.
### Supported Database Schemes and Aliases
The following are the [Go SQL drivers][go-sql] that `usql` supports, the
associated database, scheme / build tag, and scheme aliases:
| Database | Scheme / Tag | Scheme Aliases | Driver Package / Notes |
| -------------------- | --------------- | ----------------------------------------------- | --------------------------------------------------------------------------- |
| PostgreSQL | `postgres` | `pg`, `pgsql`, `postgresql` | [github.com/lib/pq][d-postgres] |
| MySQL | `mysql` | `my`, `maria`, `aurora`, `mariadb`, `percona` | [github.com/go-sql-driver/mysql][d-mysql] |
| Microsoft SQL Server | `sqlserver` | `ms`, `mssql`, `azuresql` | [github.com/microsoft/go-mssqldb][d-sqlserver] |
| Oracle Database | `oracle` | `or`, `ora`, `oci`, `oci8`, `odpi`, `odpi-c` | [github.com/sijms/go-ora/v2][d-oracle] |
| SQLite3 | `sqlite3` | `sq`, `sqlite`, `file` | [github.com/mattn/go-sqlite3][d-sqlite3] [†][f-cgo] |
| ClickHouse | `clickhouse` | `ch` | [github.com/ClickHouse/clickhouse-go/v2][d-clickhouse] |
| CSVQ | `csvq` | `cs`, `csv`, `tsv`, `json` | [github.com/mithrandie/csvq-driver][d-csvq] |
| | | | |
| Alibaba MaxCompute | `maxcompute` | `mc` | [sqlflow.org/gomaxcompute][d-maxcompute] |
| Alibaba Tablestore | `ots` | `ot`, `tablestore` | [github.com/aliyun/aliyun-tablestore-go-sql-driver][d-ots] |
| Apache Avatica | `avatica` | `av`, `phoenix` | [github.com/apache/calcite-avatica-go/v5][d-avatica] |
| Apache H2 | `h2` | | [github.com/jmrobles/h2go][d-h2] |
| Apache Hive | `hive` | `hi`, `hive2` | [sqlflow.org/gohive][d-hive] |
| Apache Ignite | `ignite` | `ig`, `gridgain` | [github.com/amsokol/ignite-go-client/sql][d-ignite] |
| Apache Impala | `impala` | `im` | [github.com/sclgo/impala-go][d-impala] |
| AWS Athena | `athena` | `s3`, `aws`, `awsathena` | [github.com/uber/athenadriver/go][d-athena] |
| Azure CosmosDB | `cosmos` | `cm`, `gocosmos` | [github.com/btnguyen2k/gocosmos][d-cosmos] |
| Cassandra | `cassandra` | `ca`, `scy`, `scylla`, `datastax`, `cql` | [github.com/MichaelS11/go-cql-driver][d-cassandra] |
| ChaiSQL | `chai` | `ci`, `genji`, `chaisql` | [github.com/chaisql/chai][d-chai] |
| Couchbase | `couchbase` | `n1`, `n1ql` | [github.com/couchbase/go_n1ql][d-couchbase] |
| Cznic QL | `ql` | `cznic`, `cznicql` | [modernc.org/ql][d-ql] |
| Databend | `databend` | `dd`, `bend` | [github.com/datafuselabs/databend-go][d-databend] |
| Databricks | `databricks` | `br`, `brick`, `bricks`, `databrick` | [github.com/databricks/databricks-sql-go][d-databricks] |
| DuckDB | `duckdb` | `dk`, `ddb`, `duck`, `file` | [github.com/duckdb/duckdb-go/v2][d-duckdb] [†][f-cgo] |
| DynamoDb | `dynamodb` | `dy`, `dyn`, `dynamo`, `dynamodb` | [github.com/btnguyen2k/godynamo][d-dynamodb] |
| Exasol | `exasol` | `ex`, `exa` | [github.com/exasol/exasol-driver-go][d-exasol] |
| Firebird | `firebird` | `fb`, `firebirdsql` | [github.com/nakagami/firebirdsql][d-firebird] |
| FlightSQL | `flightsql` | `fl`, `flight` | [github.com/apache/arrow/go/v17/arrow/flight/flightsql/driver][d-flightsql] |
| Google BigQuery | `bigquery` | `bq` | [gorm.io/driver/bigquery/driver][d-bigquery] |
| Google Spanner | `spanner` | `sp` | [github.com/googleapis/go-sql-spanner][d-spanner] |
| Microsoft ADODB | `adodb` | `ad`, `ado` | [github.com/mattn/go-adodb][d-adodb] |
| ModernC SQLite3 | `moderncsqlite` | `mq`, `modernsqlite` | [modernc.org/sqlite][d-moderncsqlite] |
| MySQL MyMySQL | `mymysql` | `zm`, `mymy` | [github.com/ziutek/mymysql/godrv][d-mymysql] |
| Netezza | `netezza` | `nz`, `nzgo` | [github.com/IBM/nzgo/v12][d-netezza] |
| PostgreSQL PGX | `pgx` | `px` | [github.com/jackc/pgx/v5/stdlib][d-pgx] |
| Presto | `presto` | `pr`, `prs`, `prestos`, `prestodb`, `prestodbs` | [github.com/prestodb/presto-go-client/presto][d-presto] |
| RamSQL | `ramsql` | `rm`, `ram` | [github.com/proullon/ramsql/driver][d-ramsql] |
| SAP ASE | `sapase` | `ax`, `ase`, `tds` | [github.com/thda/tds][d-sapase] |
| SAP HANA | `saphana` | `sa`, `sap`, `hana`, `hdb` | [github.com/SAP/go-hdb/driver][d-saphana] |
| Snowflake | `snowflake` | `sf` | [github.com/snowflakedb/gosnowflake][d-snowflake] |
| Trino | `trino` | `tr`, `trs`, `trinos` | [github.com/trinodb/trino-go-client/trino][d-trino] |
| Vertica | `vertica` | `ve` | [github.com/vertica/vertica-sql-go][d-vertica] |
| VoltDB | `voltdb` | `vo`, `vdb`, `volt` | [github.com/VoltDB/voltdb-client-go/voltdbclient][d-voltdb] |
| YDB | `ydb` | `yd`, `yds`, `ydbs` | [github.com/ydb-platform/ydb-go-sdk/v3][d-ydb] |
| | | | |
| GO DRiver for ORacle | `godror` | `gr` | [github.com/godror/godror][d-godror] [†][f-cgo] |
| ODBC | `odbc` | `od` | [github.com/alexbrainman/odbc][d-odbc] [†][f-cgo] |
| | | | |
| Amazon Redshift | `postgres` | `rs`, `redshift` | [github.com/lib/pq][d-postgres] [‡][f-wire] |
| CockroachDB | `postgres` | `cr`, `cdb`, `crdb`, `cockroach`, `cockroachdb` | [github.com/lib/pq][d-postgres] [‡][f-wire] |
| OLE ODBC | `adodb` | `oo`, `ole`, `oleodbc` | [github.com/mattn/go-adodb][d-adodb] [‡][f-wire] |
| SingleStore MemSQL | `mysql` | `me`, `memsql` | [github.com/go-sql-driver/mysql][d-mysql] [‡][f-wire] |
| TiDB | `mysql` | `ti`, `tidb` | [github.com/go-sql-driver/mysql][d-mysql] [‡][f-wire] |
| Vitess Database | `mysql` | `vt`, `vitess` | [github.com/go-sql-driver/mysql][d-mysql] [‡][f-wire] |
| | | | |
| | | | |
| | | | |
| **NO DRIVERS** | `no_base` | | _no base drivers (useful for development)_ |
| **MOST DRIVERS** | `most` | | _all stable drivers_ |
| **ALL DRIVERS** | `all` | | _all drivers, excluding bad drivers_ |
| **BAD DRIVERS** | `bad` | | _bad drivers (broken/non-working drivers)_ |
| **NO <TAG>** | `no_` | | _exclude driver with ``_ |
[d-adodb]: https://github.com/mattn/go-adodb
[d-athena]: https://github.com/uber/athenadriver
[d-avatica]: https://github.com/apache/calcite-avatica-go
[d-bigquery]: https://github.com/go-gorm/bigquery
[d-cassandra]: https://github.com/MichaelS11/go-cql-driver
[d-chai]: https://github.com/chaisql/chai
[d-clickhouse]: https://github.com/ClickHouse/clickhouse-go
[d-cosmos]: https://github.com/btnguyen2k/gocosmos
[d-couchbase]: https://github.com/couchbase/go_n1ql
[d-csvq]: https://github.com/mithrandie/csvq-driver
[d-databend]: https://github.com/datafuselabs/databend-go
[d-databricks]: https://github.com/databricks/databricks-sql-go
[d-duckdb]: https://github.com/duckdb/duckdb-go
[d-dynamodb]: https://github.com/btnguyen2k/godynamo
[d-exasol]: https://github.com/exasol/exasol-driver-go
[d-firebird]: https://github.com/nakagami/firebirdsql
[d-flightsql]: https://github.com/apache/arrow/tree/main/go/arrow/flight/flightsql/driver
[d-godror]: https://github.com/godror/godror
[d-h2]: https://github.com/jmrobles/h2go
[d-hive]: https://github.com/sql-machine-learning/gohive
[d-ignite]: https://github.com/amsokol/ignite-go-client
[d-impala]: https://github.com/sclgo/impala-go
[d-maxcompute]: https://github.com/sql-machine-learning/gomaxcompute
[d-moderncsqlite]: https://gitlab.com/cznic/sqlite
[d-mymysql]: https://github.com/ziutek/mymysql
[d-mysql]: https://github.com/go-sql-driver/mysql
[d-netezza]: https://github.com/IBM/nzgo
[d-odbc]: https://github.com/alexbrainman/odbc
[d-oracle]: https://github.com/sijms/go-ora
[d-ots]: https://github.com/aliyun/aliyun-tablestore-go-sql-driver
[d-pgx]: https://github.com/jackc/pgx
[d-postgres]: https://github.com/lib/pq
[d-presto]: https://github.com/prestodb/presto-go-client
[d-ql]: https://gitlab.com/cznic/ql
[d-ramsql]: https://github.com/proullon/ramsql
[d-sapase]: https://github.com/thda/tds
[d-saphana]: https://github.com/SAP/go-hdb
[d-snowflake]: https://github.com/snowflakedb/gosnowflake
[d-spanner]: https://github.com/googleapis/go-sql-spanner
[d-sqlite3]: https://github.com/mattn/go-sqlite3
[d-sqlserver]: https://github.com/microsoft/go-mssqldb
[d-trino]: https://github.com/trinodb/trino-go-client
[d-vertica]: https://github.com/vertica/vertica-sql-go
[d-voltdb]: https://github.com/VoltDB/voltdb-client-go
[d-ydb]: https://github.com/ydb-platform/ydb-go-sdk
[f-cgo]: #f-cgo "Requires CGO"
[f-wire]: #f-wire "Wire compatible"
† Requires CGO
‡ Wire compatible (see respective driver)
Any of the protocol schemes/aliases above can be used in conjunction when
connecting to a database via the command-line or with the [`\connect` and
`\copy` commands][commands]:
```sh
# connect to a vitess database:
$ usql vt://user:pass@host:3306/mydatabase
$ usql
(not connected)=> \c vitess://user:pass@host:3306/mydatabase
$ usql
(not connected)=> \copy csvq://. pg://localhost/ 'select * ....' 'myTable'
```
See [the section below on connecting to databases][connecting] for further
details building DSNs/URLs for use with `usql`.
## Using
After [installing][], `usql` can be used similarly to the following:
```sh
# connect to a postgres database
$ usql postgres://booktest@localhost/booktest
# connect to an oracle database
$ usql oracle://user:pass@host/oracle.sid
# connect to a postgres database and run the commands contained in script.sql
$ usql pg://localhost/ -f script.sql
```
### Command-line Options
Supported command-line options:
```sh
$ usql --help
usql, the universal command-line interface for SQL databases
Usage:
usql [flags]... [DSN]
Arguments:
DSN database url or connection name
Flags:
-c, --command COMMAND run only single command (SQL or internal) and exit
-f, --file FILE execute commands from file and exit
-w, --no-password never prompt for password
-X, --no-init do not execute initialization scripts (aliases: --no-rc --no-psqlrc --no-usqlrc)
-o, --out FILE output file
-W, --password force password prompt (should happen automatically)
-1, --single-transaction execute as a single transaction (if non-interactive)
-v, --set NAME=VALUE set variable NAME to VALUE (see \set command, aliases: --var --variable)
-N, --cset NAME=DSN set named connection NAME to DSN (see \cset command)
-P, --pset VAR=ARG set printing option VAR to ARG (see \pset command)
-F, --field-separator FIELD-SEPARATOR field separator for unaligned and CSV output (default "|" and ",")
-R, --record-separator RECORD-SEPARATOR record separator for unaligned and CSV output (default \n)
-T, --table-attr TABLE-ATTR set HTML table tag attributes (e.g., width, border)
-A, --no-align unaligned table output mode
-H, --html HTML table output mode
-t, --tuples-only print rows only
-x, --expanded turn on expanded table output
-z, --field-separator-zero set field separator for unaligned and CSV output to zero byte
-0, --record-separator-zero set record separator for unaligned and CSV output to zero byte
-J, --json JSON output mode
-C, --csv CSV output mode
-G, --vertical vertical output mode
-q, --quiet run quietly (no messages, only query output)
--config string config file
-V, --version output version information, then exit
-?, --help show this help, then exit
```
### Connecting to Databases
`usql` opens a database connection by [parsing a URL][dburl] and passing the
resulting connection string to [a database driver][databases]. Database
connection strings (aka "data source name" or DSNs) have the same parsing rules
as URLs, and can be passed to `usql` via command-line, or to the [`\connect`,
`\c`, and `\copy` commands][commands].
Database connections can be defined with [the `\cset` command][connection-vars]
or in [the `config.yaml` configuration file][config].
#### Database Connection Strings
Database connection strings look like the following:
```txt
driver+transport://user:pass@host/dbname?opt1=a&opt2=b
driver:/path/to/file
/path/to/file
name
```
Where the above are:
| Component | Description |
| ------------------------------- | ------------------------------------------------------------------------------------ |
| `driver` | driver scheme name or scheme alias |
| `transport` | `tcp`, `udp`, `unix` or driver name (for ODBC and ADODB) |
| `user` | username |
| `pass` | password |
| `host` | hostname |
| `dbname` [±][f-path] | database name, instance, or service name/ID |
| `?opt1=a&...` | additional database driver options (see respective SQL driver for available options) |
| `/path/to/file` | a path on disk |
| `name` | a connection name set by [`\cset`][connection-vars] or in [`config.yaml`][config] |
[f-path]: #f-path "URL Paths for Databases"
± Some databases, such as Microsoft SQL Server, or Oracle
Database support a path component (ie, /dbname) in the form
of /instance/dbname, where /instance is the
optional service identifier (aka "SID") or database instance
#### Driver Aliases
`usql` supports the same driver names and aliases as [the `dburl`
package][dburl]. Databases have at least one or more aliases. See [`dburl`'s
scheme documentation][dburl-schemes] for a list of all supported aliases.
##### Short Aliases
All database drivers have a two character short form that is usually the first
two letters of the database driver. For example, `pg` for `postgres`, `my` for
`mysql`, `ms` for `sqlserver`, `or` for `oracle`, or `sq` for `sqlite3`.
#### Passing Driver Options
Driver options are specified as standard URL query options in the form of
`?opt1=a&opt2=b`. Refer to the [relevant database driver's documentation][databases]
for available options.
#### Paths on Disk
If a URL does not have a `driver:` scheme, `usql` will check if it is a path on
disk. If the path exists, `usql` will attempt to use an appropriate database
driver to open the path.
When the path is a Unix Domain Socket, `usql` will attempt to open it with the
MySQL driver. When the path is a directory, `usql` will attempt to open it
using the PostgreSQL driver. And, lastly, when the path is a regular file,
`usql` will attempt to open the file using the SQLite3 or DuckDB drivers.
#### Driver Defaults
As with URLs, most components in the URL are optional and many components can
be left out. `usql` will attempt connecting using defaults where possible:
```sh
# connect to postgres using the local $USER and the unix domain socket in /var/run/postgresql
$ usql pg://
```
See the relevant documentation [on database drivers][databases] for more
information.
### Connection Examples
The following are example connection strings and additional ways to connect to
databases using `usql`:
```sh
# connect to a postgres database
$ usql pg://user:pass@host/dbname
$ usql pgsql://user:pass@host/dbname
$ usql postgres://user:pass@host:port/dbname
$ usql pg://
$ usql /var/run/postgresql
$ usql pg://user:pass@host/dbname?sslmode=disable # Connect without SSL
# connect to a mysql database
$ usql my://user:pass@host/dbname
$ usql mysql://user:pass@host:port/dbname
$ usql my://
$ usql /var/run/mysqld/mysqld.sock
# connect to a sqlserver database
$ usql sqlserver://user:pass@host/instancename/dbname
$ usql ms://user:pass@host/dbname
$ usql ms://user:pass@host/instancename/dbname
$ usql mssql://user:pass@host:port/dbname
$ usql ms://
# connect to a sqlserver database using Windows domain authentication
$ runas /user:ACME\wiley /netonly "usql mssql://host/dbname/"
# connect to a oracle database
$ usql or://user:pass@host/sid
$ usql oracle://user:pass@host:port/sid
$ usql or://
# connect to a cassandra database
$ usql ca://user:pass@host/keyspace
$ usql cassandra://host/keyspace
$ usql cql://host/
$ usql ca://
# connect to a sqlite database that exists on disk
$ usql dbname.sqlite3
# Note: when connecting to a SQLite database, if the "driver://" or
# "driver:" scheme/alias is omitted, the file must already exist on disk.
#
# if the file does not yet exist, the URL must incorporate file:, sq:, sqlite3:,
# or any other recognized sqlite3 driver alias to force usql to create a new,
# empty database at the specified path:
$ usql sq://path/to/dbname.sqlite3
$ usql sqlite3://path/to/dbname.sqlite3
$ usql file:/path/to/dbname.sqlite3
# connect to a adodb ole resource (windows only)
$ usql adodb://Microsoft.Jet.OLEDB.4.0/myfile.mdb
$ usql "adodb://Microsoft.ACE.OLEDB.12.0/?Extended+Properties=\"Text;HDR=NO;FMT=Delimited\""
# connect to a named connection in $HOME/.config/usql/config.yaml
$ cat $HOME/.config/usql/config.yaml
connections:
my_named_connection: sqlserver://user:pass@localhost/
$ usql my_named_connection
# connect with ODBC driver (requires building with odbc tag)
$ cat /etc/odbcinst.ini
[DB2]
Description=DB2 driver
Driver=/opt/db2/clidriver/lib/libdb2.so
FileUsage = 1
DontDLClose = 1
[PostgreSQL ANSI]
Description=PostgreSQL ODBC driver (ANSI version)
Driver=psqlodbca.so
Setup=libodbcpsqlS.so
Debug=0
CommLog=1
UsageCount=1
# connect to db2, postgres databases using odbc config above
$ usql odbc+DB2://user:pass@localhost/dbname
$ usql odbc+PostgreSQL+ANSI://user:pass@localhost/dbname?TraceFile=/path/to/trace.log
```
See the [section on connection variables][connection-vars] for information on
defining connection names.
### Executing Queries and Commands
The interactive interpreter reads queries and [backslash meta (`\`) commands][commands],
sending the query to the connected database:
```sh
$ usql sqlite://example.sqlite3
Connected with driver sqlite3 (SQLite3 3.17.0)
Type "help" for help.
sq:example.sqlite3=> create table test (test_id int, name string);
CREATE TABLE
sq:example.sqlite3=> insert into test (test_id, name) values (1, 'hello');
INSERT 1
sq:example.sqlite3=> select * from test;
test_id | name
+---------+-------+
1 | hello
(1 rows)
sq:example.sqlite3=> select * from test
sq:example.sqlite3-> \p
select * from test
sq:example.sqlite3-> \g
test_id | name
+---------+-------+
1 | hello
(1 rows)
sq:example.sqlite3=> \c postgres://booktest@localhost
error: pq: 28P01: password authentication failed for user "booktest"
Enter password:
Connected with driver postgres (PostgreSQL 9.6.6)
pg:booktest@localhost=> select * from authors;
author_id | name
+-----------+----------------+
1 | Unknown Master
2 | blah
3 | foobar
(3 rows)
pg:booktest@localhost=>
```
Commands may accept one or more parameter, and can be quoted using either `'`
or `"`. Command parameters [may also be backticked][backticks].
### Backslash Commands
`usql` supports interleaved backslash (`\`) meta commands to modify or alter
the way that `usql` interprets queries, formats its output, and changes the
resulting interactive flow.
```sh
(not connected)=> \c postgres://user:pass@localhost
pg:user@localhost=> select * from my_table \G
```
Available backslash meta commands can be displayed with `\?`:
```sh
$ usql
Type "help" for help.
(not connected)=> \?
General
\q quit usql
\quit alias for \q
\copyright show usage and distribution terms for usql
\drivers show database drivers available to usql
Help
\? [commands] show help on usql's meta (backslash) commands
\? options show help on usql command-line options
\? variables show help on special usql variables
Connection
\c DSN or \c NAME connect to dsn or named database connection
\c DRIVER PARAMS... connect to database with driver and parameters
\connect alias for \c
\Z close (disconnect) database connection
\disconnect alias for \Z
\password [USER] change password for user
\passwd alias for \password
\conninfo display information about the current database connection
Query Execute
\g [(OPTIONS)] [FILE] or ; execute query (and send results to file or |pipe)
\go alias for \g
\G [(OPTIONS)] [FILE] as \g, but forces vertical output mode
\ego alias for \G
\gx [(OPTIONS)] [FILE] as \g, but forces expanded output mode
\gexec execute query and execute each value of the result
\gset [PREFIX] execute query and store results in usql variables
\bind [PARAM]... set query parameters
\timing [on|off] toggle timing of commands
Query View
\crosstab [(OPTIONS)] [COLUMNS] execute query and display results in crosstab
\crosstabview alias for \crosstab
\xtab alias for \crosstab
\chart CHART [(OPTIONS)] execute query and display results as a chart
\watch [(OPTIONS)] [INTERVAL] execute query every specified interval
Query Buffer
\e [-raw|-exec] [FILE] [LINE] edit the query buffer, raw (non-interpolated) buffer, the
exec buffer, or a file with external editor
\edit alias for \e
\p [-raw|-exec] show the contents of the query buffer, the raw
(non-interpolated) buffer or the exec buffer
\print alias for \p
\raw alias for \p
\exec alias for \p
\w [-raw|-exec] FILE write the contents of the query buffer, raw
(non-interpolated) buffer, or exec buffer to file
\write alias for \w
\r reset (clear) the query buffer
\reset alias for \r
Informational
\d[S+] [NAME] list tables, views, and sequences or describe table, view,
sequence, or index
\da[S+] [PATTERN] list aggregates
\df[S+] [PATTERN] list functions
\di[S+] [PATTERN] list indexes
\dm[S+] [PATTERN] list materialized views
\dn[S+] [PATTERN] list schemas
\dp[S] [PATTERN] list table, view, and sequence access privileges
\ds[S+] [PATTERN] list sequences
\dt[S+] [PATTERN] list tables
\dv[S+] [PATTERN] list views
\l[+] list databases
\ss[+] [TABLE|QUERY] [k] show stats for a table or a query
Variables
\set [NAME [VALUE]] set usql application variable, or show all usql application
variables if no parameters
\unset NAME unset (delete) usql application variable
\pset [NAME [VALUE]] set table print formatting option, or show all print
formatting options if no parameters
\a toggle between unaligned and aligned output mode
\C [TITLE] set table title, or unset if none
\f [SEPARATOR] show or set field separator for unaligned query output
\H toggle HTML output mode
\T [ATTRIBUTES] set HTML tag attributes, or unset if none
\t [on|off] show only rows
\x [on|off|auto] toggle expanded output
\cset [NAME [URL]] set named connection, or show all named connections if no
parameters
\cset NAME DRIVER PARAMS... set named connection for driver and parameters
\prompt [-TYPE] VAR [PROMPT] prompt user to set application variable
Input/Output
\echo [-n] [MESSAGE]... write message to standard output (-n for no newline)
\qecho [-n] [MESSAGE]... write message to \o output stream (-n for no newline)
\warn [-n] [MESSAGE]... write message to standard error (-n for no newline)
\o [FILE] send all query results to file or |pipe
\out alias for \o
\copy SRC DST QUERY TABLE copy results of query from source database into table on
destination database
\copy SRC DST QUERY TABLE(A,...) copy results of query from source database into table's
columns on destination database
Control/Conditional
\i FILE execute commands from file
\include alias for \i
\ir FILE as \i, but relative to location of current script
\include_relative alias for \ir
\if EXPR begin conditional block
\elif EXPR alternative within current conditional block
\else final alternative within current conditional block
\endif end conditional block
Transaction
\begin [-read-only [ISOLATION]] begin transaction, with optional isolation level
\commit commit current transaction
\rollback rollback (abort) current transaction
\abort alias for \rollback
Operating System/Environment
\! [COMMAND] execute command in shell or start interactive shell
\cd [DIR] change the current working directory
\getenv VARNAME ENVVAR fetch environment variable
\setenv NAME [VALUE] set or unset environment variable
```
Parameters passed to commands [can be backticked][backticks].
## Features and Compatibility
An overview of `usql`'s features, functionality, and compatibility with `psql`:
- [Configuration][config]
- [Variables][variables]
- [Backticks][backticks]
- [Copying Between Databases][copying]
- [Syntax Highlighting][highlighting]
- [Time Formatting][timefmt]
- [Context Completion][completion]
- [Host Connection Information](#host-connection-information)
- [Passwords][usqlpass]
- [Runtime Configuration (RC) File][usqlrc]
The `usql` project's goal is to support as much of `psql`'s core features and
functionality, and aims to be as compatible as possible - [contributions are
always appreciated][contributing]!
#### Configuration
During its initialization phase, `usql` reads a standard [YAML configuration][yaml]
file [`config.yaml`](contrib/config.yaml). On Windows this is `%AppData%/usql/config.yaml`,
on macOS this is `$HOME/Library/Application Support/usql/config.yaml`, and on
Linux and other Unix systems this is normally `$HOME/.config/usql/config.yaml`.
##### `connections:`
[Named connection DSNs][connecting] can be defined under `connections:` as a string
or as a map:
```yaml
connections:
my_couchbase_conn: couchbase://Administrator:P4ssw0rd@localhost
my_clickhouse_conn: clickhouse://clickhouse:P4ssw0rd@localhost
my_godror_conn:
protocol: godror
username: system
password: P4ssw0rd
hostname: localhost
port: 1521
database: free
```
Defined `connections:` can be used on the command-line with `\connect`, `\c`,
`\copy`, and [other commands][commands]:
```sh
$ usql my_godror_conn
Connected with driver godror (Oracle Database 23.0.0.0.0)
Type "help" for help.
gr:system@localhost/free=>
```
##### `init:`
An initialization script can be defined as `init:` as a string:
```yaml
init: |
\echo welcome to the jungle `date`
\set SYNTAX_HL_STYLE paraiso-dark
\set PROMPT1 '\033[32m%S%M%/%R%#\033[0m '
```
The `init:` script is commonly used to set [environment variables][variables]
or other configuration, and can be disabled on the command-line using the
`--no-init` / `-X` flag. The script will be executed prior to any `-c` /
`--command` / `-f` / `--file` flag and before starting the interactive
interpreter.
##### Other Options
Please see [`contrib/config.yaml`](contrib/config.yaml) for an overview of
available configuration options.
#### Variables
`usql` supports [runtime][runtime-vars], [connection][connection-vars], and
[display formatting][print-vars] variables that can be `\set`, `\cset`, or
`\pset` respectively.
##### Runtime Variables
Runtime variables are managed with the `\set` and `\unset` [commands][commands]:
```sh
(not connected)=> \unset FOO
(not connected)=> \set FOO bar
```
Runtime variables can be displayed with `\set`:
```sh
(not connected)=> \set
FOO = 'bar'
```
###### Variable Interpolation
When a runtime variable `NAME` has been `\set`, then `:NAME`, `:'NAME'`, and
`:"NAME"` will be interpolated into the query buffer:
```sh
pg:booktest@localhost=> \set FOO bar
pg:booktest@localhost=> select * from authors where name = :'FOO';
author_id | name
+-----------+------+
7 | bar
(1 rows)
```
Where a runtime variable is used as `:'NAME'` or `:"NAME"` the interpolated
value will be quoted using `'` or `"` respectively:
```sh
pg:booktest@localhost=> \set TBLNAME authors
pg:booktest@localhost=> \set COLNAME name
pg:booktest@localhost=> \set FOO bar
pg:booktest@localhost=> select * from :TBLNAME where :"COLNAME" = :'FOO'
```
The query buffer and interpolated values can be displayed with `\p` and
`\print`, or the raw query buffer can be displayed with `\raw`:
```sh
pg:booktest@localhost-> \p
select * from authors where "name" = 'bar'
pg:booktest@localhost-> \raw
select * from :TBLNAME where :"COLNAME" = :'FOO'
```
> **Note**
>
> Variables contained within other strings will not be interpolated:
```sh
pg:booktest@localhost=> select ':FOO';
?column?
+----------+
:FOO
(1 rows)
pg:booktest@localhost=> \p
select ':FOO';
```
##### Connection Variables
Connection variables work similarly to runtime variables, and are managed with
`\cset`. Connection variables can be used with the `\c`, `\connect`, `\copy`,
or [other commands][commands]:
```sh
(not connected)=> \cset my_conn postgres://user:pass@localhost
(not connected)=> \c my_conn
Connected with driver postgres (PostgreSQL 16.2 (Debian 16.2-1.pgdg120+2))
pg:postgres@localhost=>
```
Connection variables are not interpolated into queries. See the [configuration
section for information on defining persistent connection variables][config].
Connection variables can be displayed with `\cset`:
```sh
(not connected)=> \cset
my_conn = 'postgres://user:pass@localhost'
```
##### Display Formatting (Print) Variables
Display formatting variables can be set using `\pset` and [other
commands][commands]:
```sh
(not connected)=> \pset time Kitchen
Time display is "Kitchen" ("3:04PM").
(not connected)=> \a
Output format is unaligned.
```
Display formatting variables can be displayed with `\pset`:
```sh
(not connected)=> \pset
time Kitchen
```
##### Other Variables
Runtime behavior, such as [enabling or disabling syntax
highlighting][highlighting] can be modified through special variables like
[`SYNTAX_HL`][highlighting].
Use the `\? variables` [command][commands] to display variable help information
and to list special variables recognized by `usql`:
```sh
(not connected)=> \? variables
```
#### Backticks
[Backslash (`\`) meta commands][commands] support backticks on parameters:
```sh
(not connected)=> \echo Welcome `echo $USER` -- 'currently:' "(" `date` ")"
Welcome ken -- currently: ( Wed Jun 13 12:10:27 WIB 2018 )
(not connected)=>
```
Backticked parameters will be passed to the user's `SHELL`, exactly as written,
and can be combined with `\set`:
```sh
pg:booktest@localhost=> \set MYVAR `date`
pg:booktest@localhost=> \set
MYVAR = 'Wed Jun 13 12:17:11 WIB 2018'
pg:booktest@localhost=> \echo :MYVAR
Wed Jun 13 12:17:11 WIB 2018
pg:booktest@localhost=>
```
#### Copying Between Databases
`usql` provides a `\copy` command that reads data from a source database DSN
and writes to a destination database DSN:
```sh
(not connected)=> \cset PGDSN postgres://user:pass@localhost
(not connected)=> \cset MYDSN mysql://user:pass@localhost
(not connected)=> \copy PGDSN MYDSN 'select book_id, author_id from books' 'books(id, author_id)'
```
As demonstrated above, the `\copy` command does not require being connected to
a database, and will not modify or change the current open database connection
or state.
Any valid URL or DSN name maybe used for the source and destination database:
```sh
(not connected)=> \cset MYDSN mysql://user:pass@localhost
(not connected)=> \copy postgres://user:pass@localhost MYDSN 'select book_id, author_id from books' 'books(id, author_id)'
```
> **Note**
>
> `usql`'s `\copy` is distinct from and does not function like
> `psql`'s `\copy`.
##### Copy Parameters
The `\copy` command has two parameter forms:
```txt
\copy SRC DST QUERY TABLE
\copy SRC DST QUERY TABLE(COL1, COL2, ..., COLN)
```
Where:
- `SRC` - is the [source database URL][connecting] to connect to, and where the
`QUERY` will be executed
- `DST` - is the [destination database URL][connecting] to connect to, and where
the destination `TABLE` resides
- `QUERY` - is the query to execute on the `SRC` connection, the results of which
will be copied to `TABLE`
- `TABLE` - is the destination table name, followed by an optional SQL-like column
list of the form `(COL1, COL2, ..., COLN)`
- `(COL1, COL2, ..., COLN)` - a list of the destination column names, 1-to-N
The usual rules for [variables, interpolation, and quoting][variables] apply to
`\copy`'s parameters.
###### Quoting
`QUERY` and `TABLE` **_must_** be quoted when containing spaces:
```sh
$ usql
(not connected)=> echo :SOURCE_DSN :DESTINATION_DSN
pg://postgres:P4ssw0rd@localhost/ mysql://localhost
(not connected)=> \copy :SOURCE_DSN :DESTINATION_DSN 'select * from mySourceTable' 'myDestination(colA, colB)'
COPY 2
```
###### Column Counts
The `QUERY` **_must_** return the same number of columns as defined by
the `TABLE` expression:
```sh
$ usql
(not connected)=> \copy csvq:. sq:test.db 'select * from authors' authors
error: failed to prepare insert query: 2 values for 1 columns
(not connected)=> \copy csvq:. sq:test.db 'select name from authors' authors(name)
COPY 2
```
###### Datatype Compatibility and Casting
The `\copy` command does not attempt to perform any kind of datatype
conversion.
If a `QUERY` returns columns with different datatypes than expected by the
`TABLE`'s column, the `QUERY` can use the source database's conversion/casting
functionality to cast columns to a datatype that will work for `TABLE`'s
columns:
```sh
$ usql
(not connected)=> \copy postgres://user:pass@localhost mysql://user:pass@localhost 'SELECT uuid_column::TEXT FROM myPgTable' myMyTable
COPY 1
```
###### Importing Data from CSV
The `\copy` command is capable of importing data from CSV's (or any other
database!) using the `csvq` driver:
```sh
$ cat authors.csv
author_id,name
1,Isaac Asimov
2,Stephen King
$ cat books.csv
book_id,author_id,title
1,1,I Robot
2,2,Carrie
3,2,Cujo
$ usql
(not connected)=> -- setting variables to make connections easier
(not connected)=> \set SOURCE_DSN csvq://.
(not connected)=> \set DESTINATION_DSN sqlite3:booktest.db
(not connected)=> -- connecting to the destination and creating the schema
(not connected)=> \c :DESTINATION_DSN
Connected with driver sqlite3 (SQLite3 3.38.5)
(sq:booktest.db)=> create table authors (author_id integer, name text);
CREATE TABLE
(sq:booktest.db)=> create table books (book_id integer not null primary key autoincrement, author_id integer, title text);
CREATE TABLE
(sq:booktest.db)=> -- adding an extra row to books prior to copying
(sq:booktest.db)=> insert into books (author_id, title) values (1, 'Foundation');
INSERT 1
(sq:booktest.db)=> -- disconnecting to demonstrate that \copy opens new database connections
(sq:booktest.db)=> \disconnect
(not connected)=> -- copying data from SOURCE -> DESTINATION
(not connected)=> \copy :SOURCE_DSN :DESTINATION_DSN 'select * from authors' authors
COPY 2
(not connected)=> \copy :SOURCE_DSN :DESTINATION_DSN 'select author_id, title from books' 'books(author_id, title)'
COPY 3
(not connected)=> \c :DESTINATION_DSN
Connected with driver sqlite3 (SQLite3 3.38.5)
(sq:booktest.db)=> select * from authors;
author_id | name
-----------+--------------
1 | Isaac Asimov
2 | Stephen King
(2 rows)
sq:booktest.db=> select * from books;
book_id | author_id | title
---------+-----------+------------
1 | 1 | Foundation
2 | 1 | I Robot
3 | 2 | Carrie
4 | 2 | Cujo
(4 rows)
```
> **Note**
>
> When importing large datasets (> 1GiB) from one database to another, it is
> better to use a database's native clients and tools.
###### Reusing Connections with Copy
The `\copy` command (and all `usql` commands) [works with variables][variables].
When scripting, or when needing to perform multiple `\copy` operations from/to
multiple sources/destinations, the best practice is to `\set` connection
variables either in a script or in [the `$HOME/.usqlrc` RC script][usqlrc].
Similarly, passwords can be stored for easy reuse (and kept out of scripts) by
storing in [the `$HOME/.usqlpass` password file][usqlpass].
For example:
```sh
$ cat $HOME/.usqlpass
postgres:*:*:*:postgres:P4ssw0rd
godror:*:*:*:system:P4ssw0rd
$ usql
Type "help" for help.
(not connected)=> \set pglocal postgres://postgres@localhost:49153?sslmode=disable
(not connected)=> \set orlocal godror://system@localhost:1521/orasid
(not connected)=> \copy :pglocal :orlocal 'select staff_id, first_name from staff' 'staff(staff_id, first_name)'
COPY 18
```
#### Syntax Highlighting
Interactive queries will be syntax highlighted by default, using
[Chroma][chroma]. There are a number of [variables][] that control syntax
highlighting:
| Variable | Default | Values | Description |
| ----------------------- | ------------------------------- | ----------------- | ------------------------------------------------------------ |
| `SYNTAX_HL` | `true` | `true` or `false` | enables syntax highlighting |
| `SYNTAX_HL_FORMAT` | _dependent on terminal support_ | formatter name | [Chroma formatter name][chroma-formatter] |
| `SYNTAX_HL_OVERRIDE_BG` | `true` | `true` or `false` | enables overriding the background color of the chroma styles |
| `SYNTAX_HL_STYLE` | `monokai` | style name | [Chroma style name][chroma-style] |
The `SYNTAX_*` variables are regular `usql` variables, and can be `\set` and
`\unset`:
```sh
$ usql
(not connected)=> \set SYNTAX_HL_STYLE dracula
(not connected)=> \unset SYNTAX_HL_OVERRIDE_BG
```
#### Context Completion
When using the interactive shell, context completion is available in `usql` by
hitting the `` key. For example, hitting `` can complete some parts
of `SELECT` queries on a PostgreSQL databases:
```sh
$ usql
Connected with driver postgres (PostgreSQL 14.4 (Debian 14.4-1.pgdg110+1))
Type "help" for help.
pg:postgres@=> select * f
fetch from full outer join
```
Or, for example completing [backslash commands][commands] while connected to a
database:
```sh
$ usql my://
Connected with driver mysql (10.8.3-MariaDB-1:10.8.3+maria~jammy)
Type "help" for help.
my:root@=> \g
\g \gexec \gset \gx
```
Not all commands, contexts, or databases support completion. If you're
interested in helping to make `usql`'s completion better, see [the section
below on contributing][contributing].
Command completion can be canceled with ``.
#### Time Formatting
Some databases support time/date columns that [support formatting][go-time]. By
default, `usql` formats time/date columns as [RFC3339Nano][go-time], and can be
set using `\pset time FORMAT`:
```sh
$ usql pg://
Connected with driver postgres (PostgreSQL 13.2 (Debian 13.2-1.pgdg100+1))
Type "help" for help.
pg:postgres@=> \pset
time RFC3339Nano
pg:postgres@=> select now();
now
-----------------------------
2021-05-01T22:21:44.710385Z
(1 row)
pg:postgres@=> \pset time Kitchen
Time display is "Kitchen" ("3:04PM").
pg:postgres@=> select now();
now
---------
10:22PM
(1 row)
pg:postgres@=>
```
`usql`'s time format supports any [Go supported time format][go-time], or can
be any standard Go const name, such as `Kitchen` above. See below for an
overview of the [available time constants](#time-constants).
##### Time Constants
The following are the time constant names available in `usql`, corresponding
time format value, and example display output:
| Constant | Format | Display [↓][f-ts] |
| ----------- | ------------------------------------: | ----------------------------------: |
| ANSIC | `Mon Jan _2 15:04:05 2006` | `Wed Aug 3 20:12:48 2022` |
| UnixDate | `Mon Jan _2 15:04:05 MST 2006` | `Wed Aug 3 20:12:48 UTC 2022` |
| RubyDate | `Mon Jan 02 15:04:05 -0700 2006` | `Wed Aug 03 20:12:48 +0000 2022` |
| RFC822 | `02 Jan 06 15:04 MST` | `03 Aug 22 20:12 UTC` |
| RFC822Z | `02 Jan 06 15:04 -0700` | `03 Aug 22 20:12 +0000` |
| RFC850 | `Monday, 02-Jan-06 15:04:05 MST` | `Wednesday, 03-Aug-22 20:12:48 UTC` |
| RFC1123 | `Mon, 02 Jan 2006 15:04:05 MST` | `Wed, 03 Aug 2022 20:12:48 UTC` |
| RFC1123Z | `Mon, 02 Jan 2006 15:04:05 -0700` | `Wed, 03 Aug 2022 20:12:48 +0000` |
| RFC3339 | `2006-01-02T15:04:05Z07:00` | `2022-08-03T20:12:48Z` |
| RFC3339Nano | `2006-01-02T15:04:05.999999999Z07:00` | `2022-08-03T20:12:48.693257Z` |
| Kitchen | `3:04PM` | `8:12PM` |
| Stamp | `Jan _2 15:04:05` | `Aug 3 20:12:48` |
| StampMilli | `Jan _2 15:04:05.000` | `Aug 3 20:12:48.693` |
| StampMicro | `Jan _2 15:04:05.000000` | `Aug 3 20:12:48.693257` |
| StampNano | `Jan _2 15:04:05.000000000` | `Aug 3 20:12:48.693257000` |
[f-ts]: #f-ts "Timestamp Value"
↓ Generated using timestamp 2022-08-03T20:12:48.693257Z
#### Host Connection Information
By default, `usql` displays connection information when connecting to a
database. This might cause problems with some databases or connections. This
can be disabled by setting the system environment variable `USQL_SHOW_HOST_INFORMATION`
to `false`:
```sh
$ export USQL_SHOW_HOST_INFORMATION=false
$ usql pg://booktest@localhost
Type "help" for help.
pg:booktest@=>
```
`SHOW_HOST_INFORMATION` is a standard [`usql` variable][variables],
and can be `\set` or `\unset`. Additionally, it can be passed via the
command-line using `-v` or `--set`:
```sh
$ usql --set SHOW_HOST_INFORMATION=false pg://
Type "help" for help.
pg:booktest@=> \set SHOW_HOST_INFORMATION true
pg:booktest@=> \connect pg://
Connected with driver postgres (PostgreSQL 9.6.9)
pg:booktest@=>
```
#### Terminal Graphics
`usql` supports terminal graphics for [Kitty][kitty-graphics], [iTerm][iterm-graphics],
and [Sixel][sixel-graphics] enabled terminals using the [`github.com/kenshaw/rasterm` package][rasterm].
Terminal graphics are only available when using the interactive shell.
##### Detection and Support
`usql` will attempt to detect when terminal graphics support is available using
the `USQL_TERM_GRAPHICS`, `TERM_GRAPHICS` and other environment variables
unique to various terminals.
When support is available, the logo will be displayed at the start of an
interactive session:
##### Charts and Graphs
The [`\chart` command][chart-command] can be used to display a chart
directly in the terminal:
See [the section on the `\chart` meta command][chart-command] for details.
##### Enabling/Disabling Terminal Graphics
Terminal graphics can be forced enabled or disabled by setting the
`USQL_TERM_GRAPHICS` or the `TERM_GRAPHICS` environment variable:
```sh
# disable
$ USQL_TERM_GRAPHICS=none usql
# force iterm graphics
$ TERM_GRAPHICS=iterm usql
```
| Variable | Default | Values | Description |
| --------------- | ------- | ------------------------------------- | ------------------------------ |
| `TERM_GRAPHICS` | `` | ``, `kitty`, `iterm`, `sixel`, `none` | enables/disables term graphics |
##### Terminals with Graphics Support
The following terminals have been tested with `usql`:
- [WezTerm][wezterm] is a cross-platform terminal for Windows, macOS, Linux, and
many other platforms that supports [iTerm][iterm-graphics] graphics
- [iTerm2][iterm2] is a macOS terminal that supports [iTerm][iterm-graphics]
graphics
- [kitty][kitty] is a terminal for Linux, macOS, and various BSDs that supports
[Kitty][kitty-graphics] graphics
- [foot][foot] is a Wayland terminal for Linux (and other Wayland hosts) that
supports [Sixel][sixel-graphics] graphics
Additional terminals that support [Sixel][sixel-graphics] graphics are
catalogued on the [Are We Sixel Yet?][arewesixelyet] website.
#### Passwords
`usql` supports reading passwords for databases from a `.usqlpass` file
contained in the user's `HOME` directory at startup:
```sh
$ cat $HOME/.usqlpass
# format is:
# protocol:host:port:dbname:user:pass
postgres:*:*:*:booktest:booktest
$ usql pg://
Connected with driver postgres (PostgreSQL 9.6.9)
Type "help" for help.
pg:booktest@=>
```
While the `.usqlpass` functionality will not be removed, it is recommended to
[define named connections][connection-vars] preferably via [the `config.yaml`
file][config].
> **Note**
>
> The `.usqlpass` file cannot be readable by other users, and the permissions
> should be set accordingly:
```sh
chmod 0600 ~/.usqlpass
```
#### Runtime Configuration (RC) File
`usql` supports executing a `.usqlrc` runtime configuration (RC) file contained
in the user's `HOME` directory:
```sh
$ cat $HOME/.usqlrc
\echo WELCOME TO THE JUNGLE `date`
\set SYNTAX_HL_STYLE paraiso-dark
-- set color prompt (default is prompt is "%S%m%/%R%#" )
\set PROMPT1 "\033[32m%S%m%/%R%#\033[0m"
$ usql
WELCOME TO THE JUNGLE Thu Jun 14 02:36:53 WIB 2018
Type "help" for help.
(not connected)=> \set
SYNTAX_HL_STYLE = 'paraiso-dark'
(not connected)=>
```
The `.usqlrc` file is read at startup in the same way as a file passed on the
command-line with `-f` / `--file`. It is commonly used to set startup
environment variables and settings.
RC-file execution can be temporarily disabled at startup by passing `-X` or
`--no-init` on the command-line:
```sh
$ usql --no-init pg://
```
While the `.usqlrc` functionality will not be removed, it is recommended to set
an `init` script in [the `config.yaml` file][config].
## Additional Notes
The following are additional notes and miscellania related to `usql`:
### Release Builds
[Release builds][releases] are built with the `most` build tag and with
additional [SQLite3 build tags (see: `build.sh`)](build.sh).
### macOS
The recommended installation method on macOS is [via `brew`][via Homebrew] due
to the way library dependencies for the `sqlite3` driver are done on macOS. If
the following (or similar) error is encountered when attempting to run `usql`:
```sh
$ usql
dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicuuc.68.dylib
Referenced from: /Users/user/.local/bin/usql
Reason: image not found
Abort trap: 6
```
Then missing library dependency can be fixed by installing
[`icu4c`](http://site.icu-project.org) using `brew`:
```sh
$ brew install icu4c
Running `brew update --auto-update`...
==> Downloading ...
...
$ usql
(not connected)=>
```
## Contributing
`usql` is currently a WIP, and is aiming towards a 1.0 release soon.
Well-written PRs are always welcome -- and there is a clear backlog of issues
marked `help wanted` on the GitHub issue tracker! For [technical details on
contributing, see CONTRIBUTING.md](CONTRIBUTING.md).
[_Pick up an issue today, and submit a PR tomorrow!_][help-wanted]
## Related Projects
- [dburl][dburl] - Go package providing a standard, URL-style mechanism for parsing
and opening database connection URLs
- [xo][xo] - Go command-line tool to generate Go code from a database schema
[dburl]: https://github.com/xo/dburl
[dburl-schemes]: https://github.com/xo/dburl#protocol-schemes-and-aliases
[go-time]: https://pkg.go.dev/time#pkg-constants
[go-sql]: https://pkg.go.dev/database/sql
[homebrew]: https://brew.sh/
[xo]: https://github.com/xo/xo
[xo-tap]: https://github.com/xo/homebrew-xo
[chroma]: https://github.com/alecthomas/chroma
[chroma-formatter]: https://github.com/alecthomas/chroma#formatters
[chroma-style]: https://xyproto.github.io/splash/docs/all.html
[help-wanted]: https://github.com/xo/usql/issues?q=is:open+is:issue+label:%22help+wanted%22
[aur]: https://aur.archlinux.org/packages/usql
[yay]: https://github.com/Jguer/yay
[arch-makepkg]: https://wiki.archlinux.org/title/makepkg
[backticks]: #backticks "Backticks"
[config]: #configuration "Configuration"
[commands]: #backslash-commands "Backslash Commands"
[completion]: #context-completion "Context Completion"
[connecting]: #connecting-to-databases "Connecting to Databases"
[contributing]: #contributing "Contributing"
[copying]: #copying-between-databases "Copying Between Databases"
[highlighting]: #syntax-highlighting "Syntax Highlighting"
[termgraphics]: #terminal-graphics "Terminal Graphics"
[timefmt]: #time-formatting "Time Formatting"
[usqlpass]: #passwords "Passwords"
[usqlrc]: #runtime-configuration-rc-file "Runtime Configuration File"
[variables]: #variables "Variables"
[runtime-vars]: #runtime-variables "Runtime Variables"
[connection-vars]: #connection-variables "Connection Variables"
[print-vars]: #display-formatting-(print)-variables "Display Formatting (print) Variables"
[kitty-graphics]: https://sw.kovidgoyal.net/kitty/graphics-protocol.html
[iterm-graphics]: https://iterm2.com/documentation-images.html
[sixel-graphics]: https://saitoha.github.io/libsixel/
[rasterm]: https://github.com/kenshaw/rasterm
[wezterm]: https://wezfurlong.org/wezterm/
[iterm2]: https://iterm2.com
[foot]: https://codeberg.org/dnkl/foot
[kitty]: https://sw.kovidgoyal.net/kitty/
[arewesixelyet]: https://www.arewesixelyet.com
[chart-command]: #chart-command "\\chart meta command"
[yaml]: https://yaml.org
================================================
FILE: build.sh
================================================
#!/bin/bash
set -e
SRC=$(realpath $(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd))
NAME=$(basename $SRC)
VER=
STATIC=0
FORCE=0
CHECK=1
INSTALL=0
BUILDONLY=0
VERBOSE=false
CGO_ENABLED=1
LDNAME=github.com/xo/usql/text.CommandName
LDVERSION=github.com/xo/usql/text.CommandVersion
PLATFORM=$(go env GOOS)
ARCH=$(go env GOARCH)
GOARCH=$ARCH
TAGS=(
most
sqlite_app_armor
sqlite_fts5
sqlite_introspect
sqlite_json1
sqlite_math_functions
sqlite_stat4
sqlite_vtable
)
latest_tag() {
# get latest tag version
pushd $SRC &> /dev/null
git tag -l|grep -E '^v[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?$'|sort -r -V|head -1||:
popd &> /dev/null
}
OPTIND=1
while getopts "a:v:sfnibxt:r" opt; do
case "$opt" in
a) ARCH=$OPTARG ;;
v) VER=$OPTARG ;;
s) STATIC=1 ;;
f) FORCE=1 ;;
n) CHECK=0 ;;
i) INSTALL=1 ;;
b) BUILDONLY=1 ;;
x) VERBOSE=true ;;
t) TAGS=($OPTARG) ;;
r) VER=$(latest_tag) ;;
esac
done
# neither -v or -r specified, or -v=master, set FORCE and VER
if [[ "$VER" = "" || "$VER" == "master" ]]; then
VER=0.0.0-dev
FORCE=1
fi
VER="${VER#v}"
BUILD=$SRC/build
DIR=$BUILD/$PLATFORM/$ARCH/$VER
TAR=tar
EXT=tar.bz2
BIN=$DIR/$NAME
case $PLATFORM in
linux)
TAGS+=(no_adodb)
;;
windows)
EXT=zip
BIN=$BIN.exe
;;
darwin)
TAGS+=(no_adodb)
TAR=gtar
;;
esac
OUT=$DIR/$NAME-$VER-$PLATFORM-$ARCH.$EXT
CARCH=
QEMUARCH=
GNUTYPE=
CC=
CXX=
EXTLD=g++
if [[ "$PLATFORM" == "linux" && "$ARCH" != "$GOARCH" ]]; then
case $ARCH in
arm) CARCH=armhf QEMUARCH=arm GNUTYPE=gnueabihf ;;
arm64) CARCH=aarch64 QEMUARCH=aarch64 GNUTYPE=gnu ;;
*)
echo "error: unknown arch $ARCH"
exit 1
;;
esac
LDARCH=$CARCH
if [[ "$ARCH" == "arm" ]]; then
TAGS+=(no_netezza no_chai)
if [ -d /usr/arm-linux-$GNUTYPE ]; then
LDARCH=arm
elif [ -d /usr/arm-none-linux-$GNUTYPE ]; then
LDARCH=arm-none
fi
fi
CC=$LDARCH-linux-$GNUTYPE-gcc
CXX=$LDARCH-linux-$GNUTYPE-c++
EXTLD=$LDARCH-linux-$GNUTYPE-g++
fi
if [[ "$PLATFORM" == "linux" && "$ARCH" != "amd64" ]] || [[ "$PLATFORM" == "windows" ]]; then
TAGS+=(no_duckdb)
fi
LDFLAGS=(
-s
-w
-X $LDNAME=$NAME
-X $LDVERSION=$VER
)
if [ "$STATIC" = "1" ]; then
OUT=$DIR/${NAME}_static-$VER-$PLATFORM-$ARCH.$EXT
BIN=$DIR/${NAME}_static
case $PLATFORM in
linux)
TAGS+=(
netgo
osusergo
minicore_disabled
)
EXTLDFLAGS=(
-static
-lm
-ldl
)
EXTLDFLAGS="${EXTLDFLAGS[@]}"
LDFLAGS+=(
-linkmode=external
-extldflags \'$EXTLDFLAGS\'
-extld $EXTLD
)
;;
*)
echo "ERROR: fully static builds not currently supported for $PLATFORM/$ARCH"
exit 1
;;
esac
fi
# check not overwriting existing build artifacts
if [[ -e $OUT && "$FORCE" != "1" && "$INSTALL" == "0" ]]; then
echo "ERROR: $OUT exists and FORCE != 1 (try $0 -f)"
exit 1
fi
TAGS="${TAGS[@]}"
LDFLAGS="${LDFLAGS[@]}"
echo "APP: $NAME/${VER} ($PLATFORM/$ARCH)"
if [ "$STATIC" = "1" ]; then
echo "STATIC: yes"
fi
echo "BUILD TAGS: $TAGS"
echo "LDFLAGS: $LDFLAGS"
pushd $SRC &> /dev/null
if [ -f $OUT ]; then
echo "REMOVING: $OUT"
rm -rf $OUT
fi
mkdir -p $DIR
echo "BUILDING: $BIN"
# build
echo "BUILD:"
VERB=build
OUTPUT="-o $BIN"
if [ "$INSTALL" = "1" ]; then
VERB=install OUTPUT=""
elif [ "$BUILDONLY" = "1" ]; then
OUTPUT=""
fi
(set -x;
CC=$CC \
CXX=$CXX \
CGO_ENABLED=$CGO_ENABLED \
GOARCH=$ARCH \
go $VERB \
-v=$VERBOSE \
-x=$VERBOSE \
-ldflags="$LDFLAGS" \
-tags="$TAGS" \
-trimpath \
$OUTPUT
)
if [[ "$INSTALL" == "1" || "$BUILDONLY" == "1" ]]; then
exit
fi
(set -x;
file $BIN
)
if [[ "$PLATFORM" != "windows" ]]; then
(set -x;
chmod +x $BIN
)
fi
# purge disk cache
if [[ "$PLATFORM" == "darwin" && "$CI" == "true" ]]; then
(set -x;
sudo /usr/sbin/purge
)
fi
built_ver() {
if [[ "$PLATFORM" == "linux" && "$ARCH" != "$GOARCH" ]]; then
EXTRA=
if [ -d /usr/$LDARCH-linux-$GNUTYPE/libc ]; then
EXTRA="-L /usr/$LDARCH-linux-$GNUTYPE/libc"
fi
qemu-$QEMUARCH \
-L /usr/$LDARCH-linux-$GNUTYPE \
$EXTRA \
$BIN --version
elif [[ "$PLATFORM" == "darwin" && "$ARCH" != "$GOARCH" ]]; then
echo "$NAME ${VER#v}"
else
$BIN --version
fi
}
# check build
if [[ "$CHECK" == "1" ]]; then
BUILT_VER=$(built_ver)
if [ "$BUILT_VER" != "$NAME ${VER#v}" ]; then
echo -e "\n\nERROR: expected $NAME --version to report '$NAME ${VER#v}', got: '$BUILT_VER'"
exit 1
fi
echo "REPORTED: $BUILT_VER"
fi
# pack
cp $SRC/LICENSE $DIR
case $EXT in
tar.bz2) $TAR -C $DIR -cjf $OUT $(basename $BIN) LICENSE ;;
zip) zip $OUT -j $BIN LICENSE ;;
esac
# report
echo "PACKED: $OUT ($(du -sh $OUT|awk '{print $1}'))"
case $EXT in
tar.bz2) (set -x; $TAR -jvtf $OUT) ;;
zip) (set -x; unzip -l $OUT) ;;
esac
(set -x;
sha256sum $DIR/*
)
popd &> /dev/null
================================================
FILE: contrib/adodb/adodb.sh
================================================
#!/bin/bash
rm -f example.csv
usql "adodb://Microsoft.ACE.OLEDB.12.0/?Extended+Properties=\"Text;HDR=NO;FMT=Delimited\"" \
-c "create table example.csv(f1 text, f2 text, f3 text);" \
-c "insert into example.csv(f1, f2, f3) values ('a', 'b', 'c');" \
-c "select * from example.csv;"
================================================
FILE: contrib/adodb/usql-config
================================================
================================================
FILE: contrib/cassandra/podman-config
================================================
NAME=cassandra
IMAGE=docker.io/usql/cassandra
PUBLISH=9042:9042
================================================
FILE: contrib/cassandra/test.sql
================================================
USE cycling;
CREATE KEYSPACE IF NOT EXISTS cycling WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };
// Q1:
-- Find a cyclist's name given an ID number
// CREATE TABLE SIMPLE PRIMARY KEY
CREATE TABLE cycling.cyclist_name ( id UUID PRIMARY KEY, lastname text, firstname text );
INSERT INTO cycling.cyclist_name (id, lastname, firstname) VALUES (5b6962dd-3f90-4c93-8f61-eabfa4a803e2, 'VOS','Marianne');
INSERT INTO cycling.cyclist_name (id, lastname, firstname) VALUES (e7cd5752-bc0d-4157-a80f-7523add8dbcd, 'VAN DER BREGGEN','Anna');
INSERT INTO cycling.cyclist_name (id, lastname, firstname) VALUES (e7ae5cf3-d358-4d99-b900-85902fda9bb0, 'FRAME','Alex');
INSERT INTO cycling.cyclist_name (id, lastname, firstname) VALUES (220844bf-4860-49d6-9a4b-6b5d3a79cbfb, 'TIRALONGO','Paolo');
INSERT INTO cycling.cyclist_name (id, lastname, firstname) VALUES (6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47, 'KRUIKSWIJK','Steven');
INSERT INTO cycling.cyclist_name (id, lastname, firstname) VALUES (fb372533-eb95-4bb4-8685-6ef61e994caa, 'MATTHEWS', 'Michael');
SELECT * FROM cycling.cyclist_name;
SELECT lastname, firstname FROM cycling.cyclist_name WHERE id = 6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47;
-- Q2:
-- Find cyclists that fit a particular category
// CREATE TABLE CLUSTERING ORDER, PRIMARY KEY: PARTITION KEY + 1 CLUSTERING COLUMN, SIMPLE WHERE QUERY
CREATE TABLE cycling.cyclist_category ( category text, points int, id UUID, lastname text, PRIMARY KEY (category, points)) WITH CLUSTERING ORDER BY (points DESC);
INSERT INTO cycling.cyclist_category (category, points, id, lastname) VALUES ('GC',1269,220844bf-4860-49d6-9a4b-6b5d3a79cbfb,'TIRALONGO');
INSERT INTO cycling.cyclist_category (category, points, id, lastname) VALUES ('One-day-races',367,220844bf-4860-49d6-9a4b-6b5d3a79cbfb,'TIRALONGO');
INSERT INTO cycling.cyclist_category (category, points, id, lastname) VALUES ('Time-trial',182,220844bf-4860-49d6-9a4b-6b5d3a79cbfb,'TIRALONGO');
INSERT INTO cycling.cyclist_category (category, points, id, lastname) VALUES ('Sprint',0,220844bf-4860-49d6-9a4b-6b5d3a79cbfb,'TIRALONGO');
INSERT INTO cycling.cyclist_category (category, points, id, lastname) VALUES ('GC',1324,6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47,'KRUIJSWIJK');
INSERT INTO cycling.cyclist_category (category, points, id, lastname) VALUES ('One-day-races',198,6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47,'KRUIJSWIJK');
INSERT INTO cycling.cyclist_category (category, points, id, lastname) VALUES ('Sprint',39,6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47,'KRUIJSWIJK');
INSERT INTO cycling.cyclist_category (category, points, id, lastname) VALUES ('Time-trial',3,6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47,'KRUIJSWIJK');
SELECT * FROM cycling.cyclist_category;
SELECT lastname, points FROM cycling.cyclist_category WHERE category = 'One-day-races';
-- Q3:
-- Store race information by year and race name using a COMPOSITE PARTITION KEY
CREATE TABLE cycling.rank_by_year_and_name ( race_year int, race_name text, cyclist_name text, rank int, PRIMARY KEY ((race_year, race_name), rank) );
INSERT INTO cycling.rank_by_year_and_name (race_year, race_name, cyclist_name, rank) VALUES (2015, 'Tour of Japan - Stage 4 - Minami > Shinshu', 'Benjamin PRADES', 1);
INSERT INTO cycling.rank_by_year_and_name (race_year, race_name, cyclist_name, rank) VALUES (2015, 'Tour of Japan - Stage 4 - Minami > Shinshu', 'Adam PHELAN', 2);
INSERT INTO cycling.rank_by_year_and_name (race_year, race_name, cyclist_name, rank) VALUES (2015, 'Tour of Japan - Stage 4 - Minami > Shinshu', 'Thomas LEBAS', 3);
INSERT INTO cycling.rank_by_year_and_name (race_year, race_name, cyclist_name, rank) VALUES (2015, 'Giro d''Italia - Stage 11 - Forli > Imola', 'Ilnur ZAKARIN', 1);
INSERT INTO cycling.rank_by_year_and_name (race_year, race_name, cyclist_name, rank) VALUES (2015, 'Giro d''Italia - Stage 11 - Forli > Imola', 'Carlos BETANCUR', 2);
INSERT INTO cycling.rank_by_year_and_name (race_year, race_name, cyclist_name, rank) VALUES (2014, '4th Tour of Beijing', 'Phillippe GILBERT', 1);
INSERT INTO cycling.rank_by_year_and_name (race_year, race_name, cyclist_name, rank) VALUES (2014, '4th Tour of Beijing', 'Daniel MARTIN', 2);
INSERT INTO cycling.rank_by_year_and_name (race_year, race_name, cyclist_name, rank) VALUES (2014, '4th Tour of Beijing', 'Johan Esteban CHAVES', 3);
SELECT * FROM cycling.rank_by_year_and_name;
SELECT * FROM cycling.rank_by_year_and_name WHERE race_year=2015 AND race_name='Tour of Japan - Stage 4 - Minami > Shinshu';
-- New C* 3.6
-- PER PARTITION LIMIT
-- To get the Top Two for each race_year-race_name pair
SELECT * FROM cycling.rank_by_year_and_name PER PARTITION LIMIT 2;
-- Q4:
-- Find a cyclist's id given lastname and firstname
-- Another CREATE TABLE using COMPOSITE PARTITION KEY
-- 2i INDEX ALSO GOOD FOR THIS TABLE
CREATE TABLE cycling.cyclist_id ( lastname text, firstname text, age int, id UUID, PRIMARY KEY ((lastname, firstname), age) );
INSERT INTO cycling.cyclist_id (lastname, firstname, age, id) VALUES ('EENKHOORN','Pascal',18, ffdfa2a7-5fc6-49a7-bfdc-3fcdcfdd7156);
INSERT INTO cycling.cyclist_id (lastname, firstname, age, id) VALUES ('WELTEN','Bram',18, 18f471bf-f631-4bc4-a9a2-d6f6cf5ea503);
INSERT INTO cycling.cyclist_id (lastname, firstname, age, id) VALUES ('COSTA','Adrien',17, 15a116fc-b833-4da6-ab9a-4a7775752836);
SELECT * FROM cycling.cyclist_id WHERE lastname = 'COSTA' AND firstname = 'Adrien';
-- If you want to search by age, an index can be added
CREATE INDEX c_age ON cycling.cyclist_id (age);
SELECT * FROM cycling.cyclist_id WHERE age = 18;
-- Q5:
-- Display flag for riders
-- CREATE TABLE WITH STATIC COLUMN, example uses an integer to identify flag, but it could be a blob
CREATE TABLE cycling.country_flag (country text, cyclist_name text, flag int STATIC, PRIMARY KEY (country, cyclist_name));
INSERT INTO cycling.country_flag (country, cyclist_name, flag) VALUES ('Belgium', 'Jacques', 1);
INSERT INTO cycling.country_flag (country, cyclist_name) VALUES ('Belgium', 'Andre');
INSERT INTO cycling.country_flag (country, cyclist_name, flag) VALUES ('France', 'Andre', 2);
INSERT INTO cycling.country_flag (country, cyclist_name, flag) VALUES ('France', 'George', 3);
-- USE SELECT REPEATEDLY TO SHOW CHANGING (OR UNCHANGING) NATURE OF the column 'flag'
SELECT * FROM cycling.country_flag;
-- Q6:
-- Find all teams that a cyclist has been a member of
--CREATE TABLE WITH SET
CREATE TABLE cycling.cyclist_career_teams ( id UUID PRIMARY KEY, lastname text, teams set );
INSERT INTO cycling.cyclist_career_teams (id,lastname,teams) VALUES (5b6962dd-3f90-4c93-8f61-eabfa4a803e2, 'VOS', { 'Rabobank-Liv Woman Cycling Team','Rabobank-Liv Giant','Rabobank Women Team','Nederland bloeit' } );
INSERT INTO cycling.cyclist_career_teams (id,lastname,teams) VALUES (e7cd5752-bc0d-4157-a80f-7523add8dbcd, 'VAN DER BREGGEN', { 'Rabobank-Liv Woman Cycling Team','Sengers Ladies Cycling Team','Team Flexpoint' } );
INSERT INTO cycling.cyclist_career_teams (id,lastname,teams) VALUES (cb07baad-eac8-4f65-b28a-bddc06a0de23, 'ARMITSTEAD', { 'Boels-Dolmans Cycling Team','AA Drink - Leontien.nl','Team Garmin - Cervelo' } );
INSERT INTO cycling.cyclist_career_teams (id,lastname,teams) VALUES (1c9ebc13-1eab-4ad5-be87-dce433216d40, 'BRAND', { 'Rabobank-Liv Woman Cycling Team','Rabobank-Liv Giant','AA Drink - Leontien.nl','Leontien.nl' } );
SELECT lastname,teams FROM cycling.cyclist_career_teams;
SELECT lastname, teams FROM cycling.cyclist_career_teams WHERE id=5b6962dd-3f90-4c93-8f61-eabfa4a803e2;
-- NOT A QUERY, JUST A TABLE FOR QUERIES
-- CREATE TABLE WITH LIST FOR UPDATE
-- The SELECT statements that use this table can be found below
CREATE TABLE cycling.calendar (race_id int, race_name text, race_start_date timestamp, race_end_date timestamp, PRIMARY KEY (race_id, race_start_date, race_end_date));
INSERT INTO cycling.calendar (race_id, race_name, race_start_date, race_end_date) VALUES (100, 'Giro d''Italia','2015-05-09','2015-05-31');
INSERT INTO cycling.calendar (race_id, race_name, race_start_date, race_end_date) VALUES (101, 'Criterium du Dauphine','2015-06-07','2015-06-14');
INSERT INTO cycling.calendar (race_id, race_name, race_start_date, race_end_date) VALUES (102, 'Tour de Suisse','2015-06-13','2015-06-21');
INSERT INTO cycling.calendar (race_id, race_name, race_start_date, race_end_date) VALUES (103, 'Tour de France','2015-07-04','2015-07-26');
SELECT * FROM cycling.calendar;
-- NEW FOR C*3.6
-- Clustering columns can be used in a WHERE clause with ALLOW FILTERING without secondary indexes
-- This query uses the clustering column "race_start_date" without an index and without using the partition key
-- but using ALLOW FILTERING
SELECT * FROM cycling.calendar WHERE race_start_date='2015-06-13' ALLOW FILTERING;
-- Q7:
-- Find all calendar events for a particular year and month
CREATE TABLE cycling.upcoming_calendar ( year int, month int, events list, PRIMARY KEY ( year, month ));
INSERT INTO cycling.upcoming_calendar (year, month, events) VALUES (2015, 06, ['Criterium du Dauphine','Tour de Suisse']);
INSERT INTO cycling.upcoming_calendar (year, month, events) VALUES (2015, 07, ['Tour de France']);
SELECT * FROM cycling.upcoming_calendar WHERE year=2015 AND month=06;
-- Q8:
-- SIMPLE USER-DEFINED TYPE
CREATE TYPE cycling.fullname ( firstname text, lastname text );
CREATE TABLE cycling.race_winners (race_name text, race_position int, cyclist_name FROZEN, PRIMARY KEY (race_name, race_position));
INSERT INTO cycling.race_winners (race_name, race_position, cyclist_name) VALUES ('National Championships South Africa WJ-ITT (CN)', 1, {firstname:'Frances',lastname:'DU TOUT'});
INSERT INTO cycling.race_winners (race_name, race_position, cyclist_name) VALUES ('National Championships South Africa WJ-ITT (CN)', 2, {firstname:'Lynette',lastname:'BENSON'});
INSERT INTO cycling.race_winners (race_name, race_position, cyclist_name) VALUES ('National Championships South Africa WJ-ITT (CN)', 3, {firstname:'Anja',lastname:'GERBER'});
INSERT INTO cycling.race_winners (race_name, race_position, cyclist_name) VALUES ('National Championships South Africa WJ-ITT (CN)', 4, {firstname:'Ame',lastname:'VENTER'});
INSERT INTO cycling.race_winners (race_name, race_position, cyclist_name) VALUES ('National Championships South Africa WJ-ITT (CN)', 5, {firstname:'Danielle',lastname:'VAN NIEKERK'});
SELECT * FROM cycling.race_winners WHERE race_name = 'National Championships South Africa WJ-ITT (CN)';
-- Q9:
-- Find all races for a particular cyclist
-- CREATE TYPE - User-Defined Type, race
-- CREATE TABLE WITH LIST, SIMPLE PRIMARY KEY
CREATE TYPE cycling.race (race_title text, race_date timestamp, race_time text);
CREATE TABLE cycling.cyclist_races ( id UUID PRIMARY KEY, lastname text, firstname text, races list> );
INSERT INTO cycling.cyclist_races (id, lastname, firstname, races) VALUES (5b6962dd-3f90-4c93-8f61-eabfa4a803e2, 'VOS', 'Marianne', [ {race_title:'Rabobank 7-Dorpenomloop Aalburg',race_date:'2015-05-09',race_time:'02:58:33'},{race_title:'Ronde van Gelderland',race_date:'2015-04-19',race_time:'03:22:23'}
]);
INSERT INTO cycling.cyclist_races (id, lastname, firstname, races) VALUES (e7cd5752-bc0d-4157-a80f-7523add8dbcd, 'VAN DER BREGGEN', 'Anna', [ {race_title:'Festival Luxembourgeois du cyclisme feminin Elsy Jacobs - Prologue - Garnich > Garnich',race_date:'2015-05-01',race_time:'08:13:00'},{race_title:'Fest
ival Luxembourgeois du cyclisme feminin Elsy Jacobs - Stage 2 - Garnich > Garnich',race_date:'2015-05-02',race_time:'02:41:52'},{race_title:'Festival Luxembourgeois du cyclisme feminin Elsy Jacobs - Stage 3 - Mamer > Mamer',race_date:'2015-05-03',race_time:'02:31:24'} ]);
SELECT * FROM cycling.cyclist_races;
SELECT lastname, races FROM cycling.cyclist_races WHERE id = e7cd5752-bc0d-4157-a80f-7523add8dbcd;
-- Q10:
-- Find all teams for a particular cyclist associated with the year of membership
-- teams map is map
-- CREATE TABLE WITH MAP, SIMPLE PRIMARY KEY
CREATE TABLE cycling.cyclist_teams ( id UUID PRIMARY KEY, lastname text, firstname text, teams map );
INSERT INTO cycling.cyclist_teams (id, lastname, firstname, teams) VALUES (5b6962dd-3f90-4c93-8f61-eabfa4a803e2,'VOS', 'Marianne', {2015 : 'Rabobank-Liv Woman Cycling Team', 2014 : 'Rabobank-Liv Woman Cycling Team', 2013 : 'Rabobank-Liv Giant', 2012 : 'Rabobank Women Team', 2011 : 'Nederland bloeit' });
INSERT INTO cycling.cyclist_teams (id, lastname, firstname, teams) VALUES (e7cd5752-bc0d-4157-a80f-7523add8dbcd,'VAN DER BREGGEN', 'Anna', {2015 : 'Rabobank-Liv Woman Cycling Team', 2014 : 'Rabobank-Liv Woman Cycling Team', 2013 : 'Sengers Ladies Cycling Team', 2012 : 'Sengers Ladies Cycling Team', 2009 : 'Team Flexpoint' });
INSERT INTO cycling.cyclist_teams (id, lastname, firstname, teams) VALUES (cb07baad-eac8-4f65-b28a-bddc06a0de23,'ARMITSTEAD', 'Elizabeth', {2015 : 'Boels-Dolmans Cycling Team', 2014 : 'Boels-Dolmans Cycling Team', 2013 : 'Boels-Dolmans Cycling Team', 2012 : 'AA Drink - Leontien.nl', 2011 : 'Team Garmin - Cervelo' });
SELECT lastname, firstname, teams FROM cycling.cyclist_teams;
SELECT lastname, firstname, teams FROM cycling.cyclist_teams WHERE id=5b6962dd-3f90-4c93-8f61-eabfa4a803e2;
-- Q11:
-- Find all stats for a particular cyclist
-- CREATE TYPE - UDT, basic_info
-- CREATE TABLE with UDT, SIMPLE PRIMARY KEY
CREATE TYPE cycling.basic_info ( birthday timestamp, nationality text, weight text, height text );
CREATE TABLE cycling.cyclist_stats ( id UUID, lastname text, basics FROZEN , PRIMARY KEY (id) );
INSERT INTO cycling.cyclist_stats (id, lastname, basics) VALUES (e7ae5cf3-d358-4d99-b900-85902fda9bb0, 'FRAME', { birthday:'1993-06-18',nationality:'New Zealand',weight:null,height:null });
INSERT INTO cycling.cyclist_stats (id, lastname, basics) VALUES (6cbc55e9-1943-47dc-91f2-f8f9e95992eb, 'VIGANO', { birthday:'1984-06-12',nationality:'Italy',weight:'67 kg',height:'1.82 m' });
INSERT INTO cycling.cyclist_stats (id, lastname, basics) VALUES (220844bf-4860-49d6-9a4b-6b5d3a79cbfb, 'TIRALONGO', { birthday:'1977-07-08',nationality:'Italy',weight:'63 kg',height:'1.78 m' });
SELECT * FROM cycling.cyclist_stats;
SELECT * FROM cycling.cyclist_stats WHERE id = 220844bf-4860-49d6-9a4b-6b5d3a79cbfb;
-- NEW IN C* 3.6
-- UPDATE AND DELETE single fields in UDTs with only non-collection fields
-- CHANGE "CREATE TABLE IN LAST EXAMPLE TO non-frozen
CREATE TABLE cycling.cyclist_stats ( id UUID, lastname text, basics basic_info, PRIMARY KEY (id) );
-- Now birthday can be updated separate from nationality, weight, and height
UPDATE cycling.cyclist_stats SET basics.birthday = '2000-12-12' WHERE id = 220844bf-4860-49d6-9a4b-6b5d3a79cbfb;
-- Q12:
-- Find total number of PCS points for a particular cyclist
-- CREATE TABLE WITH PRIMARY KEY: PARTITION KEY + 1 CLUSTERING COLUMN
-- USE STANDARD AGGREGATE IN QUERY
CREATE TABLE cycling.cyclist_points (id UUID, firstname text, lastname text, race_title text, race_points int, PRIMARY KEY (id, race_points ));
INSERT INTO cycling.cyclist_points (id, firstname, lastname, race_title, race_points) VALUES (e3b19ec4-774a-4d1c-9e5a-decec1e30aac, 'Giorgia','BRONZINI', 'Tour of Chongming Island World Cup', 120);
INSERT INTO cycling.cyclist_points (id, firstname, lastname, race_title, race_points) VALUES (e3b19ec4-774a-4d1c-9e5a-decec1e30aac, 'Giorgia','BRONZINI', 'Trofeo Alfredo Binda - Comune di Cittiglio', 6);
INSERT INTO cycling.cyclist_points (id, firstname, lastname, race_title, race_points) VALUES (e3b19ec4-774a-4d1c-9e5a-decec1e30aac, 'Giorgia','BRONZINI', 'Acht van Westerveld', 75);
INSERT INTO cycling.cyclist_points (id, firstname, lastname, race_title, race_points) VALUES (220844bf-4860-49d6-9a4b-6b5d3a79cbfb, 'Paolo','TIRALONGO', '98th Giro d''Italia - Stage 15', 2);
SELECT sum(race_points) FROM cycling.cyclist_points WHERE id=e3b19ec4-774a-4d1c-9e5a-decec1e30aac;
-- Q13:
-- USES TABLE cycling.cyclist_points
-- Find total number of PCS points for a particular cyclist using a user-defined function (UDF) created using java function log
-- cassandra.yaml must be modified to allow UDFs to work
-- enable_user_defined_functions: true (false by default)
-- CREATE UDF
CREATE TABLE cycling.cyclist_points (id UUID, firstname text, lastname text, race_title text, race_points double, PRIMARY KEY (id, race_points ));
INSERT INTO cycling.cyclist_points (id, firstname, lastname, race_title, race_points) VALUES (e3b19ec4-774a-4d1c-9e5a-decec1e30aac, 'Giorgia','BRONZINI', 'Tour of Chongming Island World Cup', 120);
INSERT INTO cycling.cyclist_points (id, firstname, lastname, race_title, race_points) VALUES (e3b19ec4-774a-4d1c-9e5a-decec1e30aac, 'Giorgia','BRONZINI', 'Trofeo Alfredo Binda - Comune di Cittiglio', 6);
INSERT INTO cycling.cyclist_points (id, firstname, lastname, race_title, race_points) VALUES (e3b19ec4-774a-4d1c-9e5a-decec1e30aac, 'Giorgia','BRONZINI', 'Acht van Westerveld', 75);
INSERT INTO cycling.cyclist_points (id, firstname, lastname, race_title, race_points) VALUES (220844bf-4860-49d6-9a4b-6b5d3a79cbfb, 'Paolo','TIRALONGO', '98th Giro d''Italia - Stage 15', 2);
CREATE OR REPLACE FUNCTION cycling.fLog (input double) CALLED ON NULL INPUT RETURNS double LANGUAGE java AS 'return Double.valueOf(Math.log(input.doubleValue()));';
SELECT id, lastname, fLog(race_points) FROM cycling.cyclist_points;
-- Q14:
--Find the average race_time in seconds for a particular race for a particular team.
-- CREATE UDA that computes the average value
--CREATE TABLE WITH SIMPLE PRIMARY KEY: PARTITION KEY + 2 CLUSTERING COLUMNS
CREATE OR REPLACE FUNCTION cycling.avgState ( state tuple, val int ) CALLED ON NULL INPUT RETURNS tuple LANGUAGE java AS 'if (val !=null) { state.setInt(0, state.getInt(0)+1); state.setLong(1, state.getLong(1)+val.intValue()); } return state;';
CREATE OR REPLACE FUNCTION cycling.avgFinal ( state tuple ) CALLED ON NULL INPUT RETURNS double LANGUAGE java AS 'double r = 0; if (state.getInt(0) == 0) return null; r = state.getLong(1); r/= state.getInt(0); return Double.valueOf(r);';
CREATE AGGREGATE cycling.average ( int ) SFUNC avgState STYPE tuple FINALFUNC avgFinal INITCOND (0,0);
CREATE TABLE cycling.team_average (team_name text, cyclist_name text, cyclist_time_sec int, race_title text, PRIMARY KEY (team_name, race_title,cyclist_name));
INSERT INTO cycling.team_average (team_name, cyclist_name, cyclist_time_sec, race_title) VALUES ('UnitedHealthCare Pro Cycling Womens Team','Katie HALL',11449,'Amgen Tour of California Women''s Race presented by SRAM - Stage 1 - Lake Tahoe > Lake Tahoe');
INSERT INTO cycling.team_average (team_name, cyclist_name, cyclist_time_sec, race_title) VALUES ('UnitedHealthCare Pro Cycling Womens Team','Linda VILLUMSEN',11485,'Amgen Tour of California Women''s Race presented by SRAM - Stage 1 - Lake Tahoe > Lake Tahoe');
INSERT INTO cycling.team_average (team_name, cyclist_name, cyclist_time_sec, race_title) VALUES ('UnitedHealthCare Pro Cycling Womens Team','Hannah BARNES',11490,'Amgen Tour of California Women''s Race presented by SRAM - Stage 1 - Lake Tahoe > Lake Tahoe');
INSERT INTO cycling.team_average (team_name, cyclist_name, cyclist_time_sec, race_title) VALUES ('Velocio-SRAM','Alena AMIALIUSIK',11451,'Amgen Tour of California Women''s Race presented by SRAM - Stage 1 - Lake Tahoe > Lake Tahoe');
INSERT INTO cycling.team_average (team_name, cyclist_name, cyclist_time_sec, race_title) VALUES ('Velocio-SRAM','Trixi WORRACK',11453,'Amgen Tour of California Women''s Race presented by SRAM - Stage 1 - Lake Tahoe > Lake Tahoe');
INSERT INTO cycling.team_average (team_name, cyclist_name, cyclist_time_sec, race_title) VALUES ('TWENTY16 presented by Sho-Air','Lauren KOMANSKI',11451,'Amgen Tour of California Women''s Race presented by SRAM - Stage 1 - Lake Tahoe > Lake Tahoe');
SELECT cycling.average(cyclist_time_sec) FROM cycling.team_average WHERE team_name='UnitedHealthCare Pro Cycling Womens Team' AND race_title='Amgen Tour of California Women''s Race presented by SRAM - Stage 1 - Lake Tahoe > Lake Tahoe';
-- Q15:
-- CREATE INDEX - PARTITION KEY
-- Uses cycling.rank_by_year_and_name
-- Find rank for all races for a particular race year
CREATE INDEX ryear ON cycling.rank_by_year_and_name (race_year);
-- This will not work without the index, because the table has a composite partition key
SELECT * FROM cycling.rank_by_year_and_name WHERE race_year=2015;
-- INDEX on clustering column
CREATE INDEX rrank ON cycling.rank_by_year_and_name (rank);
SELECT * FROM cycling.rank_by_year_and_name WHERE rank = 1;
-- Q16:
-- CREATE INDEX - COLLECTION - SET
-- Find all the cyclists that have been on a particular team
CREATE INDEX team ON cycling.cyclist_career_teams (teams);
SELECT * FROM cycling.cyclist_career_teams WHERE teams CONTAINS 'Nederland bloeit';
SELECT * FROM cycling.cyclist_career_teams WHERE teams CONTAINS 'Rabobank-Liv Giant';
-- Q17:
-- CREATE INDEX - COLLECTION ON MAP KEYS
-- Find all cyclist/team combinations for a particular year
-- CREATE TABLE cycling.cyclist_teams ( id UUID PRIMARY KEY, lastname text, firstname text, teams map );
CREATE INDEX team_year ON cycling.cyclist_teams (KEYS(teams));
SELECT * FROM cycling.cyclist_teams WHERE teams CONTAINS KEY 2015;
-- Q35:
-- CREATE INDEX - ENTRIES ON MAP KEYS
-- ONLY VALID FOR MAP TYPE
CREATE TABLE cycling.birthday_list (cyclist_name text PRIMARY KEY, blist map);
INSERT INTO cycling.birthday_list (cyclist_name, blist) VALUES ('Allan DAVIS', {'age':'35', 'bday':'27/07/1980', 'nation':'AUSTRALIA'});
INSERT INTO cycling.birthday_list (cyclist_name, blist) VALUES ('Claudio VANDELLI', {'age':'54', 'bday':'27/07/1961', 'nation':'ITALY'});
INSERT INTO cycling.birthday_list (cyclist_name, blist) VALUES ('Laurence BOURQUE', {'age':'23', 'bday':'27/07/1992', 'nation':'CANADA'});
INSERT INTO cycling.birthday_list (cyclist_name, blist) VALUES ('Claudio HEINEN', {'age':'23', 'bday':'27/07/1992', 'nation':'GERMANY'});
INSERT INTO cycling.birthday_list (cyclist_name, blist) VALUES ('Luc HAGENAARS', {'age':'28', 'bday':'27/07/1987', 'nation':'NETHERLANDS'});
INSERT INTO cycling.birthday_list (cyclist_name, blist) VALUES ('Toine POELS', {'age':'52', 'bday':'27/07/1963', 'nation':'NETHERLANDS'});
CREATE INDEX blist_idx ON cycling.birthday_list (ENTRIES(blist));
SELECT * FROM cycling.birthday_list WHERE blist['age'] = '23';
SELECT * FROM cycling.birthday_list WHERE blist['nation'] = 'GERMANY';
SELECT * FROM cycling.birthday_list WHERE blist['bday'] = '27/07/1992';
-- Q36:
-- CREATE INDEX - FULL ON FROZEN COLLECTION
-- ONLY VALID FOR FROZEN COLLECTIONS (SET, LIST, MAP)
CREATE TABLE cycling.race_starts (cyclist_name text PRIMARY KEY, rnumbers FROZEN>);
CREATE INDEX rnumbers_idx ON cycling.race_starts (FULL(rnumbers));
INSERT INTO cycling.race_starts (cyclist_name,rnumbers) VALUES ('Alexander KRISTOFF',[40,5,14]);
INSERT INTO cycling.race_starts (cyclist_name,rnumbers) VALUES ('Alejandro VALVERDE',[67,17,20]);
INSERT INTO cycling.race_starts (cyclist_name,rnumbers) VALUES ('Alberto CONTADOR',[61,14,7]);
INSERT INTO cycling.race_starts (cyclist_name,rnumbers) VALUES ('Christopher FROOME',[28,10,6]);
INSERT INTO cycling.race_starts (cyclist_name,rnumbers) VALUES ('John DEGENKOLB',[39,7,14]);
SELECT * FROM cycling.race_starts WHERE rnumbers = [39,7,14];
-- NOT A QUERY, JUST AN EXAMPLE
-- INSERT DATA IN JSON FORMAT
INSERT INTO cycling.cyclist_category JSON '{ "category" : "GC", "points" : 780, "id" : "829aa84a-4bba-411f-a4fb-38167a987cda", "lastname" : "SUTHERLAND" }';
-- null INSERTION EXAMPLE
INSERT INTO cycling.cyclist_category JSON '{ "category" : "Sprint", "points" : 700, "id" : "829aa84a-4bba-411f-a4fb-38167a987cda" }';
-- NOT A QUERY, JUST AN EXAMPLE
-- UPDATE SET
-- Can only be +
-- Add team to a cyclist's list of teams, order doesn't matter; this example adds it to the end
UPDATE cycling.cyclist_career_teams SET teams = teams + {'Team DSB - Ballast Nedam'} WHERE id=5b6962dd-3f90-4c93-8f61-eabfa4a803e2;
-- NOT A QUERY, JUST AN EXAMPLE
-- UPDATE LIST
-- Add events to the events list with either +/- or a specific place in the list like events[2]
UPDATE cycling.upcoming_calendar SET events = ['The Parx Casino Philly Cycling Classic'] + events WHERE year = 2015 AND month = 06;
UPDATE cycling.upcoming_calendar SET events[2] = 'Vuelta Ciclista a Venezuela' WHERE year = 2015 AND month = 06;
-- NOT A QUERY, JUST AN EXAMPLE
-- UPDATE MAP
-- Can only be +
UPDATE cycling.cyclist_teams SET teams = teams + {2009 : 'DSB Bank - Nederland bloeit'} WHERE id = 5b6962dd-3f90-4c93-8f61-eabfa4a803e2;
SELECT teams FROM cycling.cyclist_teams WHERE id = 5b6962dd-3f90-4c93-8f61-eabfa4a803e2;
UPDATE cycling.cyclist_teams SET teams[2006] = 'Team DSB - Ballast Nedam' WHERE id = 5b6962dd-3f90-4c93-8f61-eabfa4a803e2;
-- Q22:
-- UPDATE AND SELECT USING TTL
-- QUERY TO FIND TIME-TO-LIVE
-- Insert is to put in dummy record, UPDATE gives it a TTL
-- Repeated use of the SELECT will show the TTL as it counts down
INSERT INTO cycling.calendar (race_id, race_name, race_start_date, race_end_date) VALUES (200, 'placeholder', '2015-05-27', '2015-05-27') USING TTL;
UPDATE cycling.calendar USING TTL 300 SET race_name = 'dummy' WHERE race_id = 200 AND race_start_date = '2015-05-27' AND race_end_date = '2015-05-27';
SELECT TTL(race_name) FROM cycling.calendar WHERE race_id=200;
-- Q18:
-- QUERY WITH ORDER BY
-- Find all calendar events for a particular year and order by month
SELECT * FROM cycling.upcoming_calendar WHERE year= 2015 ORDER BY month DESC;
-- Q19:
-- QUERY WITH INEQUALITIES
-- Find all calendar events for a particular year between two set months
SELECT * FROM cycling.upcoming_calendar WHERE year = 2015 AND month <= 06 AND month >= 07;
-- NOT A QUERY, REALLY, JUST AN EXAMPLE
-- SELECT and GET RESULTS in JSON FORMAT
SELECT JSON month, year, events FROM cycling.upcoming_calendar;
-- Q20:
-- QUERY - WHERE ... IN SIMPLE
-- Notice the difference between using 'ORDER BY points DESC' and not using it - changes the order of reporting
-- Find all cyclists for a particular category and order by points
PAGING OFF;
SELECT * FROM cycling.cyclist_category WHERE category IN ('Time-trial', 'Sprint') ORDER BY id DESC;
PAGING OFF;
SELECT * FROM cycling.cyclist_category WHERE category IN ('Time-trial', 'Sprint') ORDER BY id ASC;
-- Q21:
-- QUERY - WHERE ... IN COMPLEX
-- Find particular races in a range of start and end dates
PAGING OFF;
SELECT * FROM cycling.calendar WHERE race_id IN (100, 101, 102) AND (race_start_date, race_end_date) IN (('2015-05-09','2015-05-31'),('2015-05-06', '2015-05-31'));
PAGING OFF;
SELECT * FROM cycling.calendar WHERE race_id IN (100, 101, 102) AND (race_start_date, race_end_date) >= ('2015-05-09','2015-05-24');
-- Q23 and 24:
-- Standard Aggregates
-- Find sum of cyclist points for a particular cyclist
-- Find the number of cyclists from a particular country
SELECT sum(race_points) FROM cycling.cyclist_points WHERE id = e3b19ec4-774a-4d1c-9e5a-decec1e30aac;
SELECT count(cyclist_name) FROM cycling.country_flag WHERE country='Belgium';
-- Q25
-- QUERY - SCAN A PARTITION
-- Find all cyclists that finished a race in a particular window of time
CREATE TABLE cycling.race_times (race_name text, cyclist_name text, race_time text, PRIMARY KEY (race_name, race_time));
INSERT INTO cycling.race_times (race_name, cyclist_name, race_time) VALUES ('17th Santos Tour Down Under', 'Rohan DENNIS', '19:15:18');
INSERT INTO cycling.race_times (race_name, cyclist_name, race_time) VALUES ('17th Santos Tour Down Under', 'Richie PORTE', '19:15:20');
INSERT INTO cycling.race_times (race_name, cyclist_name, race_time) VALUES ('17th Santos Tour Down Under', 'Cadel EVANS', '19:15:38');
INSERT INTO cycling.race_times (race_name, cyclist_name, race_time) VALUES ('17th Santos Tour Down Under', 'Tom DUMOULIN', '19:15:40');
SELECT * FROM cycling.race_times WHERE race_name = '17th Santos Tour Down Under' AND race_time >= '19:15:19' AND race_time <= '19:15:39';
-- NOT A QUERY, JUST AN EXAMPLE:
-- BATCH statement
-- Insert data into multiple tables using a BATCH statement
-- Note that what is inserted is data for the SAME cyclist, to two tables
BEGIN BATCH
INSERT INTO cycling.cyclist_name (id, lastname, firstname) VALUES (c7fceba0-c141-4207-9494-a29f9809de6f, 'PIETERS', 'Amy');
INSERT INTO cycling.cyclist_id (lastname, firstname, age, id) VALUES ('PIETERS', 'Amy', 23, c7fceba0-c141-4207-9494-a29f9809de6f);
APPLY BATCH;
SELECT * FROM cycling.cyclist_name;
SELECT * FROM cycling.cyclist_id;
-- NOT A QUERY, JUST AN EXAMPLE:
-- BATCH statement MISUSE
-- Insert data into same table, but involves multiple nodes due to partition key = id
BEGIN BATCH
INSERT INTO cycling.cyclist_name (id, lastname, firstname) VALUES (6d5f1663-89c0-45fc-8cfd-60a373b01622,'HOSKINS', 'Melissa');
INSERT INTO cycling.cyclist_name (id, lastname, firstname) VALUES (38ab64b6-26cc-4de9-ab28-c257cf011659,'FERNANDES', 'Marcia');
INSERT INTO cycling.cyclist_name (id, lastname, firstname) VALUES (9011d3be-d35c-4a8d-83f7-a3c543789ee7,'NIEWIADOMA', 'Katarzyna');
INSERT INTO cycling.cyclist_name (id, lastname, firstname) VALUES (95addc4c-459e-4ed7-b4b5-472f19a67995,'ADRIAN', 'Vera');
APPLY BATCH;
-- NOT A QUERY, JUST AN EXAMPLE:
-- BATCH statement WITH CONDITIONAL "IF NOT EXISTS"
-- EXAMPLE USES CYCLIST'S EXPENSES
CREATE TABLE cycling.cyclist_expenses ( cyclist_name text, balance float STATIC, expense_id int, amount float, description text, paid boolean, PRIMARY KEY (cyclist_name, expense_id) );
BEGIN BATCH
INSERT INTO cycling.cyclist_expenses (cyclist_name, balance) VALUES ('Vera ADRIAN', 0) IF NOT EXISTS;
INSERT INTO cycling.cyclist_expenses (cyclist_name, expense_id, amount, description, paid) VALUES ('Vera ADRIAN', 1, 7.95, 'Breakfast', false);
APPLY BATCH;
UPDATE cycling.cyclist_expenses SET balance = -7.95 WHERE cyclist_name = 'Vera ADRIAN' IF balance = 0;
-- NOT A QUERY, JUST AN EXAMPLE:
-- BATCH statement WITH CONDITIONAL "IF"
BEGIN BATCH
INSERT INTO cycling.cyclist_expenses (cyclist_name, expense_id, amount, description, paid) VALUES ('Vera ADRIAN', 2, 13.44, 'Lunch', true);
INSERT INTO cycling.cyclist_expenses (cyclist_name, expense_id, amount, description, paid) VALUES ('Vera ADRIAN', 3, 25.00, 'Dinner', false);
UPDATE cycling.cyclist_expenses SET balance = -32.95 WHERE cyclist_name = 'Vera ADRIAN' IF balance = -7.95;
APPLY BATCH;
-- NOT A QUERY, JUST AN EXAMPLE:
-- BATCH statement WITH CONDITIONAL "IF"
BEGIN BATCH
UPDATE cycling.cyclist_expenses SET balance = 0 WHERE cyclist_name = 'Vera ADRIAN' IF balance = -32.95;
UPDATE cycling.cyclist_expenses SET paid = true WHERE cyclist_name = 'Vera ADRIAN' AND expense_id = 1 IF paid = false;
UPDATE cycling.cyclist_expenses SET paid = true WHERE cyclist_name = 'Vera ADRIAN' AND expense_id = 3 IF paid = false;
APPLY BATCH;
-- NOT A QUERY, JUST AN EXAMPLE
-- LIGHTWEIGHT TRANSACTION
-- Insert or update information using a conditional statement
INSERT INTO cycling.cyclist_name (id, lastname, firstname) VALUES (c4b65263-fe58-4846-83e8-f0e1c13d518f, 'RATTO', 'Rissella') IF NOT EXISTS;
-- UPDATE USING LIGHTWEIGHT TRANSACTION
UPDATE cycling.cyclist_name SET firstname = 'Rossella' WHERE id=c4b65263-fe58-4846-83e8-f0e1c13d518f IF lastname = 'RATTO';
-- Q26
-- QUERY USING MULTIPLE INDEXES
-- DISCUSSION OF THE NEED FOR ALLOW FILTERING
-- IS THIS BETTER THAN cyclist_stats??
CREATE TABLE cycling.cyclist_alt_stats ( id UUID PRIMARY KEY, lastname text, birthday timestamp, nationality text, weight text, height text );
INSERT INTO cycling.cyclist_alt_stats (id, lastname, birthday, nationality, weight, height) VALUES (ed584e99-80f7-4b13-9a90-9dc5571e6821,'TSATEVICH', '1989-07-05', 'Russia', '64 kg', '1.69 m');
INSERT INTO cycling.cyclist_alt_stats (id, lastname, birthday, nationality, weight, height) VALUES (a9e96714-2dd0-41f9-8bd0-557196a44ecf,'ISAYCHEV', '1986-04-21', 'Russia', '80 kg', '1.88 m');
INSERT INTO cycling.cyclist_alt_stats (id, lastname, birthday, nationality, weight, height) VALUES (823ec386-2a46-45c9-be41-2425a4b7658e,'BELKOV', '1985-01-09', 'Russia', '71 kg', '1.84 m');
INSERT INTO cycling.cyclist_alt_stats (id, lastname, birthday, nationality, weight, height) VALUES (e0953617-07eb-4c82-8f91-3b2757981625,'BRUTT', '1982-01-29', 'Russia', '68 kg', '1.78 m');
INSERT INTO cycling.cyclist_alt_stats (id, lastname, birthday, nationality, weight, height) VALUES (078654a6-42fa-4142-ae43-cebdc67bd902,'LAGUTIN', '1981-01-14', 'Russia', '63 kg', '1.82 m');
INSERT INTO cycling.cyclist_alt_stats (id, lastname, birthday, nationality, weight, height) VALUES (d74d6e70-7484-4df5-8551-f5090c37f617,'GRMAY', '1991-08-25', 'Ethiopia', '63 kg', '1.75 m');
INSERT INTO cycling.cyclist_alt_stats (id, lastname, birthday, nationality, weight, height) VALUES (c09e9451-50da-483d-8108-e6bea2e827b3,'VEIKKANEN', '1981-03-29', 'Finland', '66 kg', '1.78 m');
INSERT INTO cycling.cyclist_alt_stats (id, lastname, birthday, nationality, weight, height) VALUES (f1deff54-7d96-4981-b14a-b70be4da82d2,'TLEUBAYEV', '1987-03-07', 'Kazakhstan', null, null);
INSERT INTO cycling.cyclist_alt_stats (id, lastname, birthday, nationality, weight, height) VALUES (1ba0417d-62da-4103-b710-de6fb227db6f,'PAULINHO', '1990-05-27', 'Portugal', null, null);
INSERT INTO cycling.cyclist_alt_stats (id, lastname, birthday, nationality, weight, height) VALUES (4ceb495c-55ab-4f71-83b9-81117252bb13,'DUVAL', '1990-05-27','France', null, null);
CREATE INDEX birthday_idx ON cycling.cyclist_alt_stats (birthday);
CREATE INDEX nationality_idx ON cycling.cyclist_alt_stats (nationality);
SELECT * FROM cycling.cyclist_alt_stats WHERE birthday = '1982-01-29' AND nationality = 'Russia' ALLOW FILTERING;
SELECT * FROM cycling.cyclist_alt_stats WHERE birthday = '1990-05-27' AND nationality = 'Portugal' ALLOW FILTERING;
-- Q27
-- USING EXPIRING DATA AND TTL TO DISPLAY THE LAST 3 DAYS race data
-- 3 days in seconds is 259,200
-- 2 days in seconds is 172800
-- Data will vanish when its TTL runs out
CREATE TABLE cycling.last_3_days (race_name text, year timestamp, rank int, cyclist_name text, PRIMARY KEY (year, rank, cyclist_name));
INSERT INTO cycling.last_3_days (race_name, year, rank, cyclist_name) VALUES ('Giro d''Italia Stage 16','2015-05-26',1,'Mikel Landa') USING TTL 259200;
INSERT INTO cycling.last_3_days (race_name, year, rank, cyclist_name) VALUES ('Giro d''Italia Stage 16','2015-05-26',2,'Steven Kruijswijk') USING TTL 259200;
INSERT INTO cycling.last_3_days (race_name, year, rank, cyclist_name) VALUES ('Giro d''Italia Stage 16','2015-05-26',3,'Alberto Contador') USING TTL 259200;
INSERT INTO cycling.last_3_days (race_name, year, rank, cyclist_name) VALUES ('National Championships United States - Road Race (NC)','2015-05-25',1,'Matthew Busche') USING TTL 172800;
INSERT INTO cycling.last_3_days (race_name, year, rank, cyclist_name) VALUES ('National Championships United States - Road Race (NC)','2015-05-25',2,'Joe Dombrowski') USING TTL 172800;
INSERT INTO cycling.last_3_days (race_name, year, rank, cyclist_name) VALUES ('National Championships United States - Road Race (NC)','2015-05-25',3,'Kiel Reijnen') USING TTL 172800;
SELECT TTL(race_name) FROM cycling.last_3_days;
SELECT TTL(race_name) FROM cycling.last_3_days;
SELECT * FROM cycling.last_3_days; // WILL ONLY SHOW NON-EXPIRED ROWS
-- Q28:
-- QUERY USING FUNCTION TOKEN()
-- Note how results are not consistent with dates alone; partitioner order is how they are returned
-- All 6 entries show
SELECT * FROM cycling.last_3_days WHERE token(year) > token ('2015-05-24');
-- No entries show
SELECT * FROM cycling.last_3_days WHERE token(year) > token ('2015-05-25');
-- 3 entries for 2015-05-25 show
SELECT * FROM cycling.last_3_days WHERE token(year) > token ('2015-05-26');
-- No entries show
SELECT * FROM cycling.last_3_days WHERE token(year) > token ('2015-05-27');
SELECT token(year) FROM cycling.last_3_days; //PRINTS partition hash
-- MIXED TOKEN AND PARTITION KEY
SELECT * FROM cycling.last_3_days WHERE token(year) < token ('2015-05-26') AND year IN ('2015-05-24','2015-05-25');
-- DELETE WHOLE ROW
-- Leave column(s) blank
DELETE FROM cycling.calendar WHERE race_id = 200;
-- DELETE COLUMN VALUE
DELETE lastname FROM cycling.cyclist_name WHERE id = c7fceba0-c141-4207-9494-a29f9809de6f;
UPDATE cycling.cyclist_name SET lastname = 'PIETERS' WHERE id = c7fceba0-c141-4207-9494-a29f9809de6f; // TO RESTORE THE COLUMN VALUE
-- DELETE ITEM FROM LIST
DELETE events[2] FROM cycling.upcoming_calendar WHERE year = 2015 AND month = 06;
-- DELETE ITEM FROM MAP
DELETE teams[2009] FROM cycling.cyclist_teams WHERE id=e7cd5752-bc0d-4157-a80f-7523add8dbcd;
UPDATE cycling.cyclist_teams SET teams = teams + {2009 : 'Team Flexpoint' } WHERE id = e7cd5752-bc0d-4157-a80f-7523add8dbcd; // TO RESTORE THE MAP VALUE
-- ALTER TABLE
-- ADD COLUMN
ALTER TABLE cycling.cyclist_alt_stats ADD age int;
-- ALTER TABLE WITH COLLECTION
ALTER TABLE cycling.upcoming_calendar ADD description map;
UPDATE cycling.upcoming_calendar SET description = description + {'Criterium du Dauphine' : 'Easy race', 'Tour du Suisse' : 'Hard uphill race'} WHERE year = 2015 AND month = 6;
-- ALTER TABLE AND ALTER COLUMN TYPE
-- ADDS COLUMN as varchar and then changes it to text
ALTER TABLE cycling.cyclist_alt_stats ADD favorite_color varchar;
ALTER TABLE cycling.cyclist_alt_stats ALTER favorite_color TYPE text;
-- ALTER TYPE
ALTER TYPE cycling.fullname ADD middlename text;
ALTER TYPE cycling.fullname RENAME middlename TO middleinitial;
-- TUPLE WAS USED IN THE UDA TO HOLD 2 values - see example in UDA section
-- Q29:
-- TUPLE
-- Store the latitude/longitude waypoints for the route of a race
CREATE TABLE cycling.route (race_id int, race_name text, point_id int, lat_long tuple>, PRIMARY KEY (race_id, point_id));
INSERT INTO cycling.route (race_id, race_name, point_id, lat_long) VALUES (500, '47th Tour du Pays de Vaud', 1, ('Onnens', (46.8444,6.6667)));
INSERT INTO cycling.route (race_id, race_name, point_id, lat_long) VALUES (500, '47th Tour du Pays de Vaud', 2, ('Champagne', (46.833, 6.65)));
INSERT INTO cycling.route (race_id, race_name, point_id, lat_long) VALUES (500, '47th Tour du Pays de Vaud', 3, ('Novalle', (46.833, 6.6)));
INSERT INTO cycling.route (race_id, race_name, point_id, lat_long) VALUES (500, '47th Tour du Pays de Vaud', 4, ('Vuiteboeuf', (46.8, 6.55)));
INSERT INTO cycling.route (race_id, race_name, point_id, lat_long) VALUES (500, '47th Tour du Pays de Vaud', 5, ('Baulmes', (46.7833, 6.5333)));
INSERT INTO cycling.route (race_id, race_name, point_id, lat_long) VALUES (500, '47th Tour du Pays de Vaud', 6, ('Les Clées', (46.7222, 6.5222)));
SELECT race_name, point_id, lat_long AS City_Latitude_Longitude FROM cycling.route; // Showcases 'AS' to rename column header
-- Q30:
-- QUERY USING DISTINCT
-- Find all the distinct race_id values from cycling.route
SELECT DISTINCT race_id from cycling.route;
-- Q31:
-- TUPLE
-- Rank nations by points, including top cyclist
-- tuple is rank, name, points
CREATE TABLE cycling.nation_rank ( nation text PRIMARY KEY, info tuple );
INSERT INTO cycling.nation_rank (nation, info) VALUES ('Spain', (1,'Alejandro VALVERDE' , 9054));
INSERT INTO cycling.nation_rank (nation, info) VALUES ('France', (2,'Sylvain CHAVANEL' , 6339));
INSERT INTO cycling.nation_rank (nation, info) VALUES ('Belgium', (3,'Phillippe GILBERT' , 6222));
INSERT INTO cycling.nation_rank (nation, info) VALUES ('Italy', (4,'Davide REBELLINI' , 6090));
SELECT * FROM cycling.nation_rank;
-- Q32:
-- TUPLE
-- Popular Riders
CREATE TABLE cycling.popular (rank int PRIMARY KEY, cinfo tuple );
INSERT INTO cycling.popular (rank, cinfo) VALUES (1, ('Spain', 'Mikel LANDA', 1137));
INSERT INTO cycling.popular (rank, cinfo) VALUES (2, ('Netherlands', 'Steven KRUIJSWIJK', 621));
INSERT INTO cycling.popular (rank, cinfo) VALUES (3, ('USA', 'Matthew BUSCHE', 230));
INSERT INTO cycling.popular (rank, cinfo) VALUES (4, ('Italy', 'Fabio ARU', 163));
INSERT INTO cycling.popular (rank, cinfo) VALUES (5, ('Canada', 'Ryder HESJEDAL', 148));
SELECT * FROM cycling.popular;
-- Q33:
-- COUNTER TABLE
-- Keep the count for popularity, incrementing or decrementing
CREATE TABLE cycling.popular_count ( id UUID PRIMARY KEY, popularity counter );
UPDATE cycling.popular_count SET popularity = popularity + 1 WHERE id = 6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47;
SELECT * FROM cycling.popular_count;
UPDATE cycling.popular_count SET popularity = popularity + 125 WHERE id = 6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47;
SELECT * FROM cycling.popular_count;
UPDATE cycling.popular_count SET popularity = popularity - 64 WHERE id = 6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47;
SELECT * FROM cycling.popular_count;
-- Q34:
-- Find the writetime for a column in a table
SELECT WRITETIME (firstname) FROM cycling.cyclist_points WHERE id=220844bf-4860-49d6-9a4b-6b5d3a79cbfb;
-- NOT A QUERY
-- INSERTING STRING CONSTANT USING DOUBLE DOLLAR SIGNS
INSERT INTO cycling.calendar (race_id, race_start_date, race_end_date, race_name) VALUES (201, '2015-02-18', '2015-02-22', $$Women's Tour of New Zealand$$);
-- ROLES, USERS, PERMISSIONS
-- cassandra.yaml must be changed to allow login with username and password
-- authenticator: PasswordAuthenticator (AllowAllAuthenticator by default)
-- authorizer: CassandraAuthorizer (AllowAllAuthorizer by default)
CREATE USER IF NOT EXISTS sandy WITH PASSWORD 'Ride2Win@' NOSUPERUSER;
CREATE USER chuck WITH PASSWORD 'Always1st$' SUPERUSER;
ALTER USER sandy SUPERUSER;
LIST USERS;
-- DROP USER IF EXISTS chuck;
CREATE ROLE IF NOT EXISTS team_manager WITH PASSWORD = 'RockIt4Us!';
CREATE ROLE sys_admin WITH PASSWORD = 'IcanDoIt4ll' AND LOGIN = true AND SUPERUSER = true;
ALTER ROLE sys_admin WITH PASSWORD = 'All4one1forAll' AND SUPERUSER = false;
GRANT sys_admin TO team_manager;
GRANT team_manager TO sandy;
LIST ROLES;
LIST ROLES OF sandy;
REVOKE sys_admin FROM team_manager;
REVOKE team_manager FROM sandy;
DROP ROLE IF EXISTS sys_admin;
GRANT MODIFY ON KEYSPACE cycling TO team_manager;
GRANT DESCRIBE ON ALL ROLES TO sys_admin;
GRANT AUTHORIZE ALL KEYSPACES TO sys_admin;
REVOKE SELECT ON ALL KEYSPACES FROM team_manager;
REVOKE EXECUTE ON FUNCTION cycling.fLog(double) FROM team_manager;
LIST ALL PERMISSIONS OF sandy;
LIST ALL PERMISSIONS ON cycling.cyclist_name OF chuck;
-- Q35:
-- MATERIALIZED VIEW
CREATE TABLE cycling.cyclist_mv (cid UUID PRIMARY KEY, name text, age int, birthday date, country text);
INSERT INTO cycling.cyclist_mv (cid,name,age,birthday,country) VALUES (e7ae5cf3-d358-4d99-b900-85902fda9bb0,'Alex FRAME', 22, 1993-06-18, 'New Zealand');
INSERT INTO cycling.cyclist_mv (cid,name,age,birthday,country) VALUES (220844bf-4860-49d6-9a4b-6b5d3a79cbfb,'Paolo TIRALONGO', 38, '1977-07-08', 'Italy');
INSERT INTO cycling.cyclist_mv (cid,name,age,birthday,country) VALUES (6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47,'Steven KRUIKSWIJK', 28, '1987-06-07', 'Netherlands');
INSERT INTO cycling.cyclist_mv (cid,name,age,birthday,country) VALUES (ffdfa2a7-5fc6-49a7-bfdc-3fcdcfdd7156,'Pascal EENKHOORN', 18, '1997-02-08', 'Netherlands');
INSERT INTO cycling.cyclist_mv (cid,name,age,birthday,country) VALUES (18f471bf-f631-4bc4-a9a2-d6f6cf5ea503,'Bram WELTEN', 18, '1997-03-29', 'Netherlands');
INSERT INTO cycling.cyclist_mv (cid,name,age,birthday,country) VALUES (15a116fc-b833-4da6-ab9a-4a7775752836,'Adrien COSTA', 18, '1997-08-19', 'United States');
INSERT INTO cycling.cyclist_mv (cid,name,age,birthday,country) VALUES (862cc51f-00a1-4d5a-976b-a359cab7300e,'Joakim BUKDAL', 20, '1994-09-04', 'Denmark');
INSERT INTO cycling.cyclist_mv (cid,name,age,birthday,country) VALUES (c9c9c484-5e4a-4542-8203-8d047a01b8a8,'Cristian EGIDIO', 27, '1987-09-04', 'Brazil');
INSERT INTO cycling.cyclist_mv (cid,name,age,birthday,country) VALUES (d1aad83b-be60-47a4-bd6e-069b8da0d97b,'Johannes HEIDER', 27, '1987-09-04','Germany');
CREATE MATERIALIZED VIEW cycling.cyclist_by_age AS SELECT age, birthday, name, country FROM cyclist_mv WHERE age is NOT NULL AND cid IS NOT NULL PRIMARY KEY (age, cid);
CREATE MATERIALIZED VIEW cycling.cyclist_by_country AS SELECT age, birthday, name, country FROM cyclist_mv WHERE country is NOT NULL AND cid IS NOT NULL PRIMARY KEY (country, cid);
CREATE MATERIALIZED VIEW cycling.cyclist_by_birthday AS SELECT age, birthday, name, country FROM cyclist_mv WHERE birthday is NOT NULL AND cid IS NOT NULL PRIMARY KEY (birthday, cid);
--DROP MATERIALIZED VIEW cycling.cyclist_by_age;
-- Q36:
-- USING TIMESTAMP
INSERT INTO cycling.calendar (race_id, race_name, race_start_date, race_end_date) VALUES (200, 'placeholder', '2015-05-27', '2015-05-27') USING TIMESTAMP 123456789;
-- exit
\q
================================================
FILE: contrib/cassandra/usql-config
================================================
DB="cassandra://cassandra:cassandra@localhost"
VSQL="SELECT release_version AS version FROM system.local WHERE key = 'local';"
================================================
FILE: contrib/charts/area_density_stacked.vl.json
================================================
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"title": "{{ header }}",
"width": 400,
"height": 80,
"data": {
"url": "data/penguins.json"
},
"mark": "area",
"transform": [
{
"density": "{{ x }}",
"groupby": ["Species"],
"extent": [2500, 6500]
}
],
"encoding": {
"x": {"field": "value", "type": "quantitative", "title": "{{ title_x }}"},
"y": {"field": "density", "type": "quantitative", "stack": "zero"},
"color": {"field": "{{ field_x }}", "type": "nominal"}
}
}
================================================
FILE: contrib/charts/penguins.json
================================================
[
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 39.1,
"Beak Depth (mm)": 18.7,
"Flipper Length (mm)": 181,
"Body Mass (g)": 3750,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 39.5,
"Beak Depth (mm)": 17.4,
"Flipper Length (mm)": 186,
"Body Mass (g)": 3800,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 40.3,
"Beak Depth (mm)": 18,
"Flipper Length (mm)": 195,
"Body Mass (g)": 3250,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": null,
"Beak Depth (mm)": null,
"Flipper Length (mm)": null,
"Body Mass (g)": null,
"Sex": null
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 36.7,
"Beak Depth (mm)": 19.3,
"Flipper Length (mm)": 193,
"Body Mass (g)": 3450,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 39.3,
"Beak Depth (mm)": 20.6,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3650,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 38.9,
"Beak Depth (mm)": 17.8,
"Flipper Length (mm)": 181,
"Body Mass (g)": 3625,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 39.2,
"Beak Depth (mm)": 19.6,
"Flipper Length (mm)": 195,
"Body Mass (g)": 4675,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 34.1,
"Beak Depth (mm)": 18.1,
"Flipper Length (mm)": 193,
"Body Mass (g)": 3475,
"Sex": null
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 42,
"Beak Depth (mm)": 20.2,
"Flipper Length (mm)": 190,
"Body Mass (g)": 4250,
"Sex": null
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 37.8,
"Beak Depth (mm)": 17.1,
"Flipper Length (mm)": 186,
"Body Mass (g)": 3300,
"Sex": null
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 37.8,
"Beak Depth (mm)": 17.3,
"Flipper Length (mm)": 180,
"Body Mass (g)": 3700,
"Sex": null
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 41.1,
"Beak Depth (mm)": 17.6,
"Flipper Length (mm)": 182,
"Body Mass (g)": 3200,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 38.6,
"Beak Depth (mm)": 21.2,
"Flipper Length (mm)": 191,
"Body Mass (g)": 3800,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 34.6,
"Beak Depth (mm)": 21.1,
"Flipper Length (mm)": 198,
"Body Mass (g)": 4400,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 36.6,
"Beak Depth (mm)": 17.8,
"Flipper Length (mm)": 185,
"Body Mass (g)": 3700,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 38.7,
"Beak Depth (mm)": 19,
"Flipper Length (mm)": 195,
"Body Mass (g)": 3450,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 42.5,
"Beak Depth (mm)": 20.7,
"Flipper Length (mm)": 197,
"Body Mass (g)": 4500,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 34.4,
"Beak Depth (mm)": 18.4,
"Flipper Length (mm)": 184,
"Body Mass (g)": 3325,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 46,
"Beak Depth (mm)": 21.5,
"Flipper Length (mm)": 194,
"Body Mass (g)": 4200,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 37.8,
"Beak Depth (mm)": 18.3,
"Flipper Length (mm)": 174,
"Body Mass (g)": 3400,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 37.7,
"Beak Depth (mm)": 18.7,
"Flipper Length (mm)": 180,
"Body Mass (g)": 3600,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 35.9,
"Beak Depth (mm)": 19.2,
"Flipper Length (mm)": 189,
"Body Mass (g)": 3800,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 38.2,
"Beak Depth (mm)": 18.1,
"Flipper Length (mm)": 185,
"Body Mass (g)": 3950,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 38.8,
"Beak Depth (mm)": 17.2,
"Flipper Length (mm)": 180,
"Body Mass (g)": 3800,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 35.3,
"Beak Depth (mm)": 18.9,
"Flipper Length (mm)": 187,
"Body Mass (g)": 3800,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 40.6,
"Beak Depth (mm)": 18.6,
"Flipper Length (mm)": 183,
"Body Mass (g)": 3550,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 40.5,
"Beak Depth (mm)": 17.9,
"Flipper Length (mm)": 187,
"Body Mass (g)": 3200,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 37.9,
"Beak Depth (mm)": 18.6,
"Flipper Length (mm)": 172,
"Body Mass (g)": 3150,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 40.5,
"Beak Depth (mm)": 18.9,
"Flipper Length (mm)": 180,
"Body Mass (g)": 3950,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 39.5,
"Beak Depth (mm)": 16.7,
"Flipper Length (mm)": 178,
"Body Mass (g)": 3250,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 37.2,
"Beak Depth (mm)": 18.1,
"Flipper Length (mm)": 178,
"Body Mass (g)": 3900,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 39.5,
"Beak Depth (mm)": 17.8,
"Flipper Length (mm)": 188,
"Body Mass (g)": 3300,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 40.9,
"Beak Depth (mm)": 18.9,
"Flipper Length (mm)": 184,
"Body Mass (g)": 3900,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 36.4,
"Beak Depth (mm)": 17,
"Flipper Length (mm)": 195,
"Body Mass (g)": 3325,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 39.2,
"Beak Depth (mm)": 21.1,
"Flipper Length (mm)": 196,
"Body Mass (g)": 4150,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 38.8,
"Beak Depth (mm)": 20,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3950,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 42.2,
"Beak Depth (mm)": 18.5,
"Flipper Length (mm)": 180,
"Body Mass (g)": 3550,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 37.6,
"Beak Depth (mm)": 19.3,
"Flipper Length (mm)": 181,
"Body Mass (g)": 3300,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 39.8,
"Beak Depth (mm)": 19.1,
"Flipper Length (mm)": 184,
"Body Mass (g)": 4650,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 36.5,
"Beak Depth (mm)": 18,
"Flipper Length (mm)": 182,
"Body Mass (g)": 3150,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 40.8,
"Beak Depth (mm)": 18.4,
"Flipper Length (mm)": 195,
"Body Mass (g)": 3900,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 36,
"Beak Depth (mm)": 18.5,
"Flipper Length (mm)": 186,
"Body Mass (g)": 3100,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 44.1,
"Beak Depth (mm)": 19.7,
"Flipper Length (mm)": 196,
"Body Mass (g)": 4400,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 37,
"Beak Depth (mm)": 16.9,
"Flipper Length (mm)": 185,
"Body Mass (g)": 3000,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 39.6,
"Beak Depth (mm)": 18.8,
"Flipper Length (mm)": 190,
"Body Mass (g)": 4600,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 41.1,
"Beak Depth (mm)": 19,
"Flipper Length (mm)": 182,
"Body Mass (g)": 3425,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 37.5,
"Beak Depth (mm)": 18.9,
"Flipper Length (mm)": 179,
"Body Mass (g)": 2975,
"Sex": null
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 36,
"Beak Depth (mm)": 17.9,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3450,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 42.3,
"Beak Depth (mm)": 21.2,
"Flipper Length (mm)": 191,
"Body Mass (g)": 4150,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 39.6,
"Beak Depth (mm)": 17.7,
"Flipper Length (mm)": 186,
"Body Mass (g)": 3500,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 40.1,
"Beak Depth (mm)": 18.9,
"Flipper Length (mm)": 188,
"Body Mass (g)": 4300,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 35,
"Beak Depth (mm)": 17.9,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3450,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 42,
"Beak Depth (mm)": 19.5,
"Flipper Length (mm)": 200,
"Body Mass (g)": 4050,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 34.5,
"Beak Depth (mm)": 18.1,
"Flipper Length (mm)": 187,
"Body Mass (g)": 2900,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 41.4,
"Beak Depth (mm)": 18.6,
"Flipper Length (mm)": 191,
"Body Mass (g)": 3700,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 39,
"Beak Depth (mm)": 17.5,
"Flipper Length (mm)": 186,
"Body Mass (g)": 3550,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 40.6,
"Beak Depth (mm)": 18.8,
"Flipper Length (mm)": 193,
"Body Mass (g)": 3800,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 36.5,
"Beak Depth (mm)": 16.6,
"Flipper Length (mm)": 181,
"Body Mass (g)": 2850,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 37.6,
"Beak Depth (mm)": 19.1,
"Flipper Length (mm)": 194,
"Body Mass (g)": 3750,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 35.7,
"Beak Depth (mm)": 16.9,
"Flipper Length (mm)": 185,
"Body Mass (g)": 3150,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 41.3,
"Beak Depth (mm)": 21.1,
"Flipper Length (mm)": 195,
"Body Mass (g)": 4400,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 37.6,
"Beak Depth (mm)": 17,
"Flipper Length (mm)": 185,
"Body Mass (g)": 3600,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 41.1,
"Beak Depth (mm)": 18.2,
"Flipper Length (mm)": 192,
"Body Mass (g)": 4050,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 36.4,
"Beak Depth (mm)": 17.1,
"Flipper Length (mm)": 184,
"Body Mass (g)": 2850,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 41.6,
"Beak Depth (mm)": 18,
"Flipper Length (mm)": 192,
"Body Mass (g)": 3950,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 35.5,
"Beak Depth (mm)": 16.2,
"Flipper Length (mm)": 195,
"Body Mass (g)": 3350,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 41.1,
"Beak Depth (mm)": 19.1,
"Flipper Length (mm)": 188,
"Body Mass (g)": 4100,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 35.9,
"Beak Depth (mm)": 16.6,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3050,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 41.8,
"Beak Depth (mm)": 19.4,
"Flipper Length (mm)": 198,
"Body Mass (g)": 4450,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 33.5,
"Beak Depth (mm)": 19,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3600,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 39.7,
"Beak Depth (mm)": 18.4,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3900,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 39.6,
"Beak Depth (mm)": 17.2,
"Flipper Length (mm)": 196,
"Body Mass (g)": 3550,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 45.8,
"Beak Depth (mm)": 18.9,
"Flipper Length (mm)": 197,
"Body Mass (g)": 4150,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 35.5,
"Beak Depth (mm)": 17.5,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3700,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 42.8,
"Beak Depth (mm)": 18.5,
"Flipper Length (mm)": 195,
"Body Mass (g)": 4250,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 40.9,
"Beak Depth (mm)": 16.8,
"Flipper Length (mm)": 191,
"Body Mass (g)": 3700,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 37.2,
"Beak Depth (mm)": 19.4,
"Flipper Length (mm)": 184,
"Body Mass (g)": 3900,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 36.2,
"Beak Depth (mm)": 16.1,
"Flipper Length (mm)": 187,
"Body Mass (g)": 3550,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 42.1,
"Beak Depth (mm)": 19.1,
"Flipper Length (mm)": 195,
"Body Mass (g)": 4000,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 34.6,
"Beak Depth (mm)": 17.2,
"Flipper Length (mm)": 189,
"Body Mass (g)": 3200,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 42.9,
"Beak Depth (mm)": 17.6,
"Flipper Length (mm)": 196,
"Body Mass (g)": 4700,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 36.7,
"Beak Depth (mm)": 18.8,
"Flipper Length (mm)": 187,
"Body Mass (g)": 3800,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 35.1,
"Beak Depth (mm)": 19.4,
"Flipper Length (mm)": 193,
"Body Mass (g)": 4200,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 37.3,
"Beak Depth (mm)": 17.8,
"Flipper Length (mm)": 191,
"Body Mass (g)": 3350,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 41.3,
"Beak Depth (mm)": 20.3,
"Flipper Length (mm)": 194,
"Body Mass (g)": 3550,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 36.3,
"Beak Depth (mm)": 19.5,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3800,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 36.9,
"Beak Depth (mm)": 18.6,
"Flipper Length (mm)": 189,
"Body Mass (g)": 3500,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 38.3,
"Beak Depth (mm)": 19.2,
"Flipper Length (mm)": 189,
"Body Mass (g)": 3950,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 38.9,
"Beak Depth (mm)": 18.8,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3600,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 35.7,
"Beak Depth (mm)": 18,
"Flipper Length (mm)": 202,
"Body Mass (g)": 3550,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 41.1,
"Beak Depth (mm)": 18.1,
"Flipper Length (mm)": 205,
"Body Mass (g)": 4300,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 34,
"Beak Depth (mm)": 17.1,
"Flipper Length (mm)": 185,
"Body Mass (g)": 3400,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 39.6,
"Beak Depth (mm)": 18.1,
"Flipper Length (mm)": 186,
"Body Mass (g)": 4450,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 36.2,
"Beak Depth (mm)": 17.3,
"Flipper Length (mm)": 187,
"Body Mass (g)": 3300,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 40.8,
"Beak Depth (mm)": 18.9,
"Flipper Length (mm)": 208,
"Body Mass (g)": 4300,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 38.1,
"Beak Depth (mm)": 18.6,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3700,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 40.3,
"Beak Depth (mm)": 18.5,
"Flipper Length (mm)": 196,
"Body Mass (g)": 4350,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 33.1,
"Beak Depth (mm)": 16.1,
"Flipper Length (mm)": 178,
"Body Mass (g)": 2900,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 43.2,
"Beak Depth (mm)": 18.5,
"Flipper Length (mm)": 192,
"Body Mass (g)": 4100,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 35,
"Beak Depth (mm)": 17.9,
"Flipper Length (mm)": 192,
"Body Mass (g)": 3725,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 41,
"Beak Depth (mm)": 20,
"Flipper Length (mm)": 203,
"Body Mass (g)": 4725,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 37.7,
"Beak Depth (mm)": 16,
"Flipper Length (mm)": 183,
"Body Mass (g)": 3075,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 37.8,
"Beak Depth (mm)": 20,
"Flipper Length (mm)": 190,
"Body Mass (g)": 4250,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 37.9,
"Beak Depth (mm)": 18.6,
"Flipper Length (mm)": 193,
"Body Mass (g)": 2925,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 39.7,
"Beak Depth (mm)": 18.9,
"Flipper Length (mm)": 184,
"Body Mass (g)": 3550,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 38.6,
"Beak Depth (mm)": 17.2,
"Flipper Length (mm)": 199,
"Body Mass (g)": 3750,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 38.2,
"Beak Depth (mm)": 20,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3900,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 38.1,
"Beak Depth (mm)": 17,
"Flipper Length (mm)": 181,
"Body Mass (g)": 3175,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 43.2,
"Beak Depth (mm)": 19,
"Flipper Length (mm)": 197,
"Body Mass (g)": 4775,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 38.1,
"Beak Depth (mm)": 16.5,
"Flipper Length (mm)": 198,
"Body Mass (g)": 3825,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 45.6,
"Beak Depth (mm)": 20.3,
"Flipper Length (mm)": 191,
"Body Mass (g)": 4600,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 39.7,
"Beak Depth (mm)": 17.7,
"Flipper Length (mm)": 193,
"Body Mass (g)": 3200,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 42.2,
"Beak Depth (mm)": 19.5,
"Flipper Length (mm)": 197,
"Body Mass (g)": 4275,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 39.6,
"Beak Depth (mm)": 20.7,
"Flipper Length (mm)": 191,
"Body Mass (g)": 3900,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Biscoe",
"Beak Length (mm)": 42.7,
"Beak Depth (mm)": 18.3,
"Flipper Length (mm)": 196,
"Body Mass (g)": 4075,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 38.6,
"Beak Depth (mm)": 17,
"Flipper Length (mm)": 188,
"Body Mass (g)": 2900,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 37.3,
"Beak Depth (mm)": 20.5,
"Flipper Length (mm)": 199,
"Body Mass (g)": 3775,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 35.7,
"Beak Depth (mm)": 17,
"Flipper Length (mm)": 189,
"Body Mass (g)": 3350,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 41.1,
"Beak Depth (mm)": 18.6,
"Flipper Length (mm)": 189,
"Body Mass (g)": 3325,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 36.2,
"Beak Depth (mm)": 17.2,
"Flipper Length (mm)": 187,
"Body Mass (g)": 3150,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 37.7,
"Beak Depth (mm)": 19.8,
"Flipper Length (mm)": 198,
"Body Mass (g)": 3500,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 40.2,
"Beak Depth (mm)": 17,
"Flipper Length (mm)": 176,
"Body Mass (g)": 3450,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 41.4,
"Beak Depth (mm)": 18.5,
"Flipper Length (mm)": 202,
"Body Mass (g)": 3875,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 35.2,
"Beak Depth (mm)": 15.9,
"Flipper Length (mm)": 186,
"Body Mass (g)": 3050,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 40.6,
"Beak Depth (mm)": 19,
"Flipper Length (mm)": 199,
"Body Mass (g)": 4000,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 38.8,
"Beak Depth (mm)": 17.6,
"Flipper Length (mm)": 191,
"Body Mass (g)": 3275,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 41.5,
"Beak Depth (mm)": 18.3,
"Flipper Length (mm)": 195,
"Body Mass (g)": 4300,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 39,
"Beak Depth (mm)": 17.1,
"Flipper Length (mm)": 191,
"Body Mass (g)": 3050,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 44.1,
"Beak Depth (mm)": 18,
"Flipper Length (mm)": 210,
"Body Mass (g)": 4000,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 38.5,
"Beak Depth (mm)": 17.9,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3325,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Torgersen",
"Beak Length (mm)": 43.1,
"Beak Depth (mm)": 19.2,
"Flipper Length (mm)": 197,
"Body Mass (g)": 3500,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 36.8,
"Beak Depth (mm)": 18.5,
"Flipper Length (mm)": 193,
"Body Mass (g)": 3500,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 37.5,
"Beak Depth (mm)": 18.5,
"Flipper Length (mm)": 199,
"Body Mass (g)": 4475,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 38.1,
"Beak Depth (mm)": 17.6,
"Flipper Length (mm)": 187,
"Body Mass (g)": 3425,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 41.1,
"Beak Depth (mm)": 17.5,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3900,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 35.6,
"Beak Depth (mm)": 17.5,
"Flipper Length (mm)": 191,
"Body Mass (g)": 3175,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 40.2,
"Beak Depth (mm)": 20.1,
"Flipper Length (mm)": 200,
"Body Mass (g)": 3975,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 37,
"Beak Depth (mm)": 16.5,
"Flipper Length (mm)": 185,
"Body Mass (g)": 3400,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 39.7,
"Beak Depth (mm)": 17.9,
"Flipper Length (mm)": 193,
"Body Mass (g)": 4250,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 40.2,
"Beak Depth (mm)": 17.1,
"Flipper Length (mm)": 193,
"Body Mass (g)": 3400,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 40.6,
"Beak Depth (mm)": 17.2,
"Flipper Length (mm)": 187,
"Body Mass (g)": 3475,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 32.1,
"Beak Depth (mm)": 15.5,
"Flipper Length (mm)": 188,
"Body Mass (g)": 3050,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 40.7,
"Beak Depth (mm)": 17,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3725,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 37.3,
"Beak Depth (mm)": 16.8,
"Flipper Length (mm)": 192,
"Body Mass (g)": 3000,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 39,
"Beak Depth (mm)": 18.7,
"Flipper Length (mm)": 185,
"Body Mass (g)": 3650,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 39.2,
"Beak Depth (mm)": 18.6,
"Flipper Length (mm)": 190,
"Body Mass (g)": 4250,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 36.6,
"Beak Depth (mm)": 18.4,
"Flipper Length (mm)": 184,
"Body Mass (g)": 3475,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 36,
"Beak Depth (mm)": 17.8,
"Flipper Length (mm)": 195,
"Body Mass (g)": 3450,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 37.8,
"Beak Depth (mm)": 18.1,
"Flipper Length (mm)": 193,
"Body Mass (g)": 3750,
"Sex": "MALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 36,
"Beak Depth (mm)": 17.1,
"Flipper Length (mm)": 187,
"Body Mass (g)": 3700,
"Sex": "FEMALE"
},
{
"Species": "Adelie",
"Island": "Dream",
"Beak Length (mm)": 41.5,
"Beak Depth (mm)": 18.5,
"Flipper Length (mm)": 201,
"Body Mass (g)": 4000,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 46.5,
"Beak Depth (mm)": 17.9,
"Flipper Length (mm)": 192,
"Body Mass (g)": 3500,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 50,
"Beak Depth (mm)": 19.5,
"Flipper Length (mm)": 196,
"Body Mass (g)": 3900,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 51.3,
"Beak Depth (mm)": 19.2,
"Flipper Length (mm)": 193,
"Body Mass (g)": 3650,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 45.4,
"Beak Depth (mm)": 18.7,
"Flipper Length (mm)": 188,
"Body Mass (g)": 3525,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 52.7,
"Beak Depth (mm)": 19.8,
"Flipper Length (mm)": 197,
"Body Mass (g)": 3725,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 45.2,
"Beak Depth (mm)": 17.8,
"Flipper Length (mm)": 198,
"Body Mass (g)": 3950,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 46.1,
"Beak Depth (mm)": 18.2,
"Flipper Length (mm)": 178,
"Body Mass (g)": 3250,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 51.3,
"Beak Depth (mm)": 18.2,
"Flipper Length (mm)": 197,
"Body Mass (g)": 3750,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 46,
"Beak Depth (mm)": 18.9,
"Flipper Length (mm)": 195,
"Body Mass (g)": 4150,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 51.3,
"Beak Depth (mm)": 19.9,
"Flipper Length (mm)": 198,
"Body Mass (g)": 3700,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 46.6,
"Beak Depth (mm)": 17.8,
"Flipper Length (mm)": 193,
"Body Mass (g)": 3800,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 51.7,
"Beak Depth (mm)": 20.3,
"Flipper Length (mm)": 194,
"Body Mass (g)": 3775,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 47,
"Beak Depth (mm)": 17.3,
"Flipper Length (mm)": 185,
"Body Mass (g)": 3700,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 52,
"Beak Depth (mm)": 18.1,
"Flipper Length (mm)": 201,
"Body Mass (g)": 4050,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 45.9,
"Beak Depth (mm)": 17.1,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3575,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 50.5,
"Beak Depth (mm)": 19.6,
"Flipper Length (mm)": 201,
"Body Mass (g)": 4050,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 50.3,
"Beak Depth (mm)": 20,
"Flipper Length (mm)": 197,
"Body Mass (g)": 3300,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 58,
"Beak Depth (mm)": 17.8,
"Flipper Length (mm)": 181,
"Body Mass (g)": 3700,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 46.4,
"Beak Depth (mm)": 18.6,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3450,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 49.2,
"Beak Depth (mm)": 18.2,
"Flipper Length (mm)": 195,
"Body Mass (g)": 4400,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 42.4,
"Beak Depth (mm)": 17.3,
"Flipper Length (mm)": 181,
"Body Mass (g)": 3600,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 48.5,
"Beak Depth (mm)": 17.5,
"Flipper Length (mm)": 191,
"Body Mass (g)": 3400,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 43.2,
"Beak Depth (mm)": 16.6,
"Flipper Length (mm)": 187,
"Body Mass (g)": 2900,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 50.6,
"Beak Depth (mm)": 19.4,
"Flipper Length (mm)": 193,
"Body Mass (g)": 3800,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 46.7,
"Beak Depth (mm)": 17.9,
"Flipper Length (mm)": 195,
"Body Mass (g)": 3300,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 52,
"Beak Depth (mm)": 19,
"Flipper Length (mm)": 197,
"Body Mass (g)": 4150,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 50.5,
"Beak Depth (mm)": 18.4,
"Flipper Length (mm)": 200,
"Body Mass (g)": 3400,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 49.5,
"Beak Depth (mm)": 19,
"Flipper Length (mm)": 200,
"Body Mass (g)": 3800,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 46.4,
"Beak Depth (mm)": 17.8,
"Flipper Length (mm)": 191,
"Body Mass (g)": 3700,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 52.8,
"Beak Depth (mm)": 20,
"Flipper Length (mm)": 205,
"Body Mass (g)": 4550,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 40.9,
"Beak Depth (mm)": 16.6,
"Flipper Length (mm)": 187,
"Body Mass (g)": 3200,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 54.2,
"Beak Depth (mm)": 20.8,
"Flipper Length (mm)": 201,
"Body Mass (g)": 4300,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 42.5,
"Beak Depth (mm)": 16.7,
"Flipper Length (mm)": 187,
"Body Mass (g)": 3350,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 51,
"Beak Depth (mm)": 18.8,
"Flipper Length (mm)": 203,
"Body Mass (g)": 4100,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 49.7,
"Beak Depth (mm)": 18.6,
"Flipper Length (mm)": 195,
"Body Mass (g)": 3600,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 47.5,
"Beak Depth (mm)": 16.8,
"Flipper Length (mm)": 199,
"Body Mass (g)": 3900,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 47.6,
"Beak Depth (mm)": 18.3,
"Flipper Length (mm)": 195,
"Body Mass (g)": 3850,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 52,
"Beak Depth (mm)": 20.7,
"Flipper Length (mm)": 210,
"Body Mass (g)": 4800,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 46.9,
"Beak Depth (mm)": 16.6,
"Flipper Length (mm)": 192,
"Body Mass (g)": 2700,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 53.5,
"Beak Depth (mm)": 19.9,
"Flipper Length (mm)": 205,
"Body Mass (g)": 4500,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 49,
"Beak Depth (mm)": 19.5,
"Flipper Length (mm)": 210,
"Body Mass (g)": 3950,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 46.2,
"Beak Depth (mm)": 17.5,
"Flipper Length (mm)": 187,
"Body Mass (g)": 3650,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 50.9,
"Beak Depth (mm)": 19.1,
"Flipper Length (mm)": 196,
"Body Mass (g)": 3550,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 45.5,
"Beak Depth (mm)": 17,
"Flipper Length (mm)": 196,
"Body Mass (g)": 3500,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 50.9,
"Beak Depth (mm)": 17.9,
"Flipper Length (mm)": 196,
"Body Mass (g)": 3675,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 50.8,
"Beak Depth (mm)": 18.5,
"Flipper Length (mm)": 201,
"Body Mass (g)": 4450,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 50.1,
"Beak Depth (mm)": 17.9,
"Flipper Length (mm)": 190,
"Body Mass (g)": 3400,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 49,
"Beak Depth (mm)": 19.6,
"Flipper Length (mm)": 212,
"Body Mass (g)": 4300,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 51.5,
"Beak Depth (mm)": 18.7,
"Flipper Length (mm)": 187,
"Body Mass (g)": 3250,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 49.8,
"Beak Depth (mm)": 17.3,
"Flipper Length (mm)": 198,
"Body Mass (g)": 3675,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 48.1,
"Beak Depth (mm)": 16.4,
"Flipper Length (mm)": 199,
"Body Mass (g)": 3325,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 51.4,
"Beak Depth (mm)": 19,
"Flipper Length (mm)": 201,
"Body Mass (g)": 3950,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 45.7,
"Beak Depth (mm)": 17.3,
"Flipper Length (mm)": 193,
"Body Mass (g)": 3600,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 50.7,
"Beak Depth (mm)": 19.7,
"Flipper Length (mm)": 203,
"Body Mass (g)": 4050,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 42.5,
"Beak Depth (mm)": 17.3,
"Flipper Length (mm)": 187,
"Body Mass (g)": 3350,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 52.2,
"Beak Depth (mm)": 18.8,
"Flipper Length (mm)": 197,
"Body Mass (g)": 3450,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 45.2,
"Beak Depth (mm)": 16.6,
"Flipper Length (mm)": 191,
"Body Mass (g)": 3250,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 49.3,
"Beak Depth (mm)": 19.9,
"Flipper Length (mm)": 203,
"Body Mass (g)": 4050,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 50.2,
"Beak Depth (mm)": 18.8,
"Flipper Length (mm)": 202,
"Body Mass (g)": 3800,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 45.6,
"Beak Depth (mm)": 19.4,
"Flipper Length (mm)": 194,
"Body Mass (g)": 3525,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 51.9,
"Beak Depth (mm)": 19.5,
"Flipper Length (mm)": 206,
"Body Mass (g)": 3950,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 46.8,
"Beak Depth (mm)": 16.5,
"Flipper Length (mm)": 189,
"Body Mass (g)": 3650,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 45.7,
"Beak Depth (mm)": 17,
"Flipper Length (mm)": 195,
"Body Mass (g)": 3650,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 55.8,
"Beak Depth (mm)": 19.8,
"Flipper Length (mm)": 207,
"Body Mass (g)": 4000,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 43.5,
"Beak Depth (mm)": 18.1,
"Flipper Length (mm)": 202,
"Body Mass (g)": 3400,
"Sex": "FEMALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 49.6,
"Beak Depth (mm)": 18.2,
"Flipper Length (mm)": 193,
"Body Mass (g)": 3775,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 50.8,
"Beak Depth (mm)": 19,
"Flipper Length (mm)": 210,
"Body Mass (g)": 4100,
"Sex": "MALE"
},
{
"Species": "Chinstrap",
"Island": "Dream",
"Beak Length (mm)": 50.2,
"Beak Depth (mm)": 18.7,
"Flipper Length (mm)": 198,
"Body Mass (g)": 3775,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.1,
"Beak Depth (mm)": 13.2,
"Flipper Length (mm)": 211,
"Body Mass (g)": 4500,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 50,
"Beak Depth (mm)": 16.3,
"Flipper Length (mm)": 230,
"Body Mass (g)": 5700,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 48.7,
"Beak Depth (mm)": 14.1,
"Flipper Length (mm)": 210,
"Body Mass (g)": 4450,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 50,
"Beak Depth (mm)": 15.2,
"Flipper Length (mm)": 218,
"Body Mass (g)": 5700,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 47.6,
"Beak Depth (mm)": 14.5,
"Flipper Length (mm)": 215,
"Body Mass (g)": 5400,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.5,
"Beak Depth (mm)": 13.5,
"Flipper Length (mm)": 210,
"Body Mass (g)": 4550,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.4,
"Beak Depth (mm)": 14.6,
"Flipper Length (mm)": 211,
"Body Mass (g)": 4800,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.7,
"Beak Depth (mm)": 15.3,
"Flipper Length (mm)": 219,
"Body Mass (g)": 5200,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 43.3,
"Beak Depth (mm)": 13.4,
"Flipper Length (mm)": 209,
"Body Mass (g)": 4400,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.8,
"Beak Depth (mm)": 15.4,
"Flipper Length (mm)": 215,
"Body Mass (g)": 5150,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 40.9,
"Beak Depth (mm)": 13.7,
"Flipper Length (mm)": 214,
"Body Mass (g)": 4650,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 49,
"Beak Depth (mm)": 16.1,
"Flipper Length (mm)": 216,
"Body Mass (g)": 5550,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.5,
"Beak Depth (mm)": 13.7,
"Flipper Length (mm)": 214,
"Body Mass (g)": 4650,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 48.4,
"Beak Depth (mm)": 14.6,
"Flipper Length (mm)": 213,
"Body Mass (g)": 5850,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.8,
"Beak Depth (mm)": 14.6,
"Flipper Length (mm)": 210,
"Body Mass (g)": 4200,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 49.3,
"Beak Depth (mm)": 15.7,
"Flipper Length (mm)": 217,
"Body Mass (g)": 5850,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 42,
"Beak Depth (mm)": 13.5,
"Flipper Length (mm)": 210,
"Body Mass (g)": 4150,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 49.2,
"Beak Depth (mm)": 15.2,
"Flipper Length (mm)": 221,
"Body Mass (g)": 6300,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.2,
"Beak Depth (mm)": 14.5,
"Flipper Length (mm)": 209,
"Body Mass (g)": 4800,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 48.7,
"Beak Depth (mm)": 15.1,
"Flipper Length (mm)": 222,
"Body Mass (g)": 5350,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 50.2,
"Beak Depth (mm)": 14.3,
"Flipper Length (mm)": 218,
"Body Mass (g)": 5700,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.1,
"Beak Depth (mm)": 14.5,
"Flipper Length (mm)": 215,
"Body Mass (g)": 5000,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.5,
"Beak Depth (mm)": 14.5,
"Flipper Length (mm)": 213,
"Body Mass (g)": 4400,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.3,
"Beak Depth (mm)": 15.8,
"Flipper Length (mm)": 215,
"Body Mass (g)": 5050,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 42.9,
"Beak Depth (mm)": 13.1,
"Flipper Length (mm)": 215,
"Body Mass (g)": 5000,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.1,
"Beak Depth (mm)": 15.1,
"Flipper Length (mm)": 215,
"Body Mass (g)": 5100,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 44.5,
"Beak Depth (mm)": 14.3,
"Flipper Length (mm)": 216,
"Body Mass (g)": 4100,
"Sex": null
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 47.8,
"Beak Depth (mm)": 15,
"Flipper Length (mm)": 215,
"Body Mass (g)": 5650,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 48.2,
"Beak Depth (mm)": 14.3,
"Flipper Length (mm)": 210,
"Body Mass (g)": 4600,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 50,
"Beak Depth (mm)": 15.3,
"Flipper Length (mm)": 220,
"Body Mass (g)": 5550,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 47.3,
"Beak Depth (mm)": 15.3,
"Flipper Length (mm)": 222,
"Body Mass (g)": 5250,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 42.8,
"Beak Depth (mm)": 14.2,
"Flipper Length (mm)": 209,
"Body Mass (g)": 4700,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.1,
"Beak Depth (mm)": 14.5,
"Flipper Length (mm)": 207,
"Body Mass (g)": 5050,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 59.6,
"Beak Depth (mm)": 17,
"Flipper Length (mm)": 230,
"Body Mass (g)": 6050,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 49.1,
"Beak Depth (mm)": 14.8,
"Flipper Length (mm)": 220,
"Body Mass (g)": 5150,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 48.4,
"Beak Depth (mm)": 16.3,
"Flipper Length (mm)": 220,
"Body Mass (g)": 5400,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 42.6,
"Beak Depth (mm)": 13.7,
"Flipper Length (mm)": 213,
"Body Mass (g)": 4950,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 44.4,
"Beak Depth (mm)": 17.3,
"Flipper Length (mm)": 219,
"Body Mass (g)": 5250,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 44,
"Beak Depth (mm)": 13.6,
"Flipper Length (mm)": 208,
"Body Mass (g)": 4350,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 48.7,
"Beak Depth (mm)": 15.7,
"Flipper Length (mm)": 208,
"Body Mass (g)": 5350,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 42.7,
"Beak Depth (mm)": 13.7,
"Flipper Length (mm)": 208,
"Body Mass (g)": 3950,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 49.6,
"Beak Depth (mm)": 16,
"Flipper Length (mm)": 225,
"Body Mass (g)": 5700,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.3,
"Beak Depth (mm)": 13.7,
"Flipper Length (mm)": 210,
"Body Mass (g)": 4300,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 49.6,
"Beak Depth (mm)": 15,
"Flipper Length (mm)": 216,
"Body Mass (g)": 4750,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 50.5,
"Beak Depth (mm)": 15.9,
"Flipper Length (mm)": 222,
"Body Mass (g)": 5550,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 43.6,
"Beak Depth (mm)": 13.9,
"Flipper Length (mm)": 217,
"Body Mass (g)": 4900,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.5,
"Beak Depth (mm)": 13.9,
"Flipper Length (mm)": 210,
"Body Mass (g)": 4200,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 50.5,
"Beak Depth (mm)": 15.9,
"Flipper Length (mm)": 225,
"Body Mass (g)": 5400,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 44.9,
"Beak Depth (mm)": 13.3,
"Flipper Length (mm)": 213,
"Body Mass (g)": 5100,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.2,
"Beak Depth (mm)": 15.8,
"Flipper Length (mm)": 215,
"Body Mass (g)": 5300,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.6,
"Beak Depth (mm)": 14.2,
"Flipper Length (mm)": 210,
"Body Mass (g)": 4850,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 48.5,
"Beak Depth (mm)": 14.1,
"Flipper Length (mm)": 220,
"Body Mass (g)": 5300,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.1,
"Beak Depth (mm)": 14.4,
"Flipper Length (mm)": 210,
"Body Mass (g)": 4400,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 50.1,
"Beak Depth (mm)": 15,
"Flipper Length (mm)": 225,
"Body Mass (g)": 5000,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.5,
"Beak Depth (mm)": 14.4,
"Flipper Length (mm)": 217,
"Body Mass (g)": 4900,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45,
"Beak Depth (mm)": 15.4,
"Flipper Length (mm)": 220,
"Body Mass (g)": 5050,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 43.8,
"Beak Depth (mm)": 13.9,
"Flipper Length (mm)": 208,
"Body Mass (g)": 4300,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.5,
"Beak Depth (mm)": 15,
"Flipper Length (mm)": 220,
"Body Mass (g)": 5000,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 43.2,
"Beak Depth (mm)": 14.5,
"Flipper Length (mm)": 208,
"Body Mass (g)": 4450,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 50.4,
"Beak Depth (mm)": 15.3,
"Flipper Length (mm)": 224,
"Body Mass (g)": 5550,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.3,
"Beak Depth (mm)": 13.8,
"Flipper Length (mm)": 208,
"Body Mass (g)": 4200,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.2,
"Beak Depth (mm)": 14.9,
"Flipper Length (mm)": 221,
"Body Mass (g)": 5300,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.7,
"Beak Depth (mm)": 13.9,
"Flipper Length (mm)": 214,
"Body Mass (g)": 4400,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 54.3,
"Beak Depth (mm)": 15.7,
"Flipper Length (mm)": 231,
"Body Mass (g)": 5650,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.8,
"Beak Depth (mm)": 14.2,
"Flipper Length (mm)": 219,
"Body Mass (g)": 4700,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 49.8,
"Beak Depth (mm)": 16.8,
"Flipper Length (mm)": 230,
"Body Mass (g)": 5700,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.2,
"Beak Depth (mm)": 14.4,
"Flipper Length (mm)": 214,
"Body Mass (g)": 4650,
"Sex": null
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 49.5,
"Beak Depth (mm)": 16.2,
"Flipper Length (mm)": 229,
"Body Mass (g)": 5800,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 43.5,
"Beak Depth (mm)": 14.2,
"Flipper Length (mm)": 220,
"Body Mass (g)": 4700,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 50.7,
"Beak Depth (mm)": 15,
"Flipper Length (mm)": 223,
"Body Mass (g)": 5550,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 47.7,
"Beak Depth (mm)": 15,
"Flipper Length (mm)": 216,
"Body Mass (g)": 4750,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.4,
"Beak Depth (mm)": 15.6,
"Flipper Length (mm)": 221,
"Body Mass (g)": 5000,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 48.2,
"Beak Depth (mm)": 15.6,
"Flipper Length (mm)": 221,
"Body Mass (g)": 5100,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.5,
"Beak Depth (mm)": 14.8,
"Flipper Length (mm)": 217,
"Body Mass (g)": 5200,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.4,
"Beak Depth (mm)": 15,
"Flipper Length (mm)": 216,
"Body Mass (g)": 4700,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 48.6,
"Beak Depth (mm)": 16,
"Flipper Length (mm)": 230,
"Body Mass (g)": 5800,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 47.5,
"Beak Depth (mm)": 14.2,
"Flipper Length (mm)": 209,
"Body Mass (g)": 4600,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 51.1,
"Beak Depth (mm)": 16.3,
"Flipper Length (mm)": 220,
"Body Mass (g)": 6000,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.2,
"Beak Depth (mm)": 13.8,
"Flipper Length (mm)": 215,
"Body Mass (g)": 4750,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.2,
"Beak Depth (mm)": 16.4,
"Flipper Length (mm)": 223,
"Body Mass (g)": 5950,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 49.1,
"Beak Depth (mm)": 14.5,
"Flipper Length (mm)": 212,
"Body Mass (g)": 4625,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 52.5,
"Beak Depth (mm)": 15.6,
"Flipper Length (mm)": 221,
"Body Mass (g)": 5450,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 47.4,
"Beak Depth (mm)": 14.6,
"Flipper Length (mm)": 212,
"Body Mass (g)": 4725,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 50,
"Beak Depth (mm)": 15.9,
"Flipper Length (mm)": 224,
"Body Mass (g)": 5350,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 44.9,
"Beak Depth (mm)": 13.8,
"Flipper Length (mm)": 212,
"Body Mass (g)": 4750,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 50.8,
"Beak Depth (mm)": 17.3,
"Flipper Length (mm)": 228,
"Body Mass (g)": 5600,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 43.4,
"Beak Depth (mm)": 14.4,
"Flipper Length (mm)": 218,
"Body Mass (g)": 4600,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 51.3,
"Beak Depth (mm)": 14.2,
"Flipper Length (mm)": 218,
"Body Mass (g)": 5300,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 47.5,
"Beak Depth (mm)": 14,
"Flipper Length (mm)": 212,
"Body Mass (g)": 4875,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 52.1,
"Beak Depth (mm)": 17,
"Flipper Length (mm)": 230,
"Body Mass (g)": 5550,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 47.5,
"Beak Depth (mm)": 15,
"Flipper Length (mm)": 218,
"Body Mass (g)": 4950,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 52.2,
"Beak Depth (mm)": 17.1,
"Flipper Length (mm)": 228,
"Body Mass (g)": 5400,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.5,
"Beak Depth (mm)": 14.5,
"Flipper Length (mm)": 212,
"Body Mass (g)": 4750,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 49.5,
"Beak Depth (mm)": 16.1,
"Flipper Length (mm)": 224,
"Body Mass (g)": 5650,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 44.5,
"Beak Depth (mm)": 14.7,
"Flipper Length (mm)": 214,
"Body Mass (g)": 4850,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 50.8,
"Beak Depth (mm)": 15.7,
"Flipper Length (mm)": 226,
"Body Mass (g)": 5200,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 49.4,
"Beak Depth (mm)": 15.8,
"Flipper Length (mm)": 216,
"Body Mass (g)": 4925,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.9,
"Beak Depth (mm)": 14.6,
"Flipper Length (mm)": 222,
"Body Mass (g)": 4875,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 48.4,
"Beak Depth (mm)": 14.4,
"Flipper Length (mm)": 203,
"Body Mass (g)": 4625,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 51.1,
"Beak Depth (mm)": 16.5,
"Flipper Length (mm)": 225,
"Body Mass (g)": 5250,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 48.5,
"Beak Depth (mm)": 15,
"Flipper Length (mm)": 219,
"Body Mass (g)": 4850,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 55.9,
"Beak Depth (mm)": 17,
"Flipper Length (mm)": 228,
"Body Mass (g)": 5600,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 47.2,
"Beak Depth (mm)": 15.5,
"Flipper Length (mm)": 215,
"Body Mass (g)": 4975,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 49.1,
"Beak Depth (mm)": 15,
"Flipper Length (mm)": 228,
"Body Mass (g)": 5500,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 47.3,
"Beak Depth (mm)": 13.8,
"Flipper Length (mm)": 216,
"Body Mass (g)": 4725,
"Sex": null
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.8,
"Beak Depth (mm)": 16.1,
"Flipper Length (mm)": 215,
"Body Mass (g)": 5500,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 41.7,
"Beak Depth (mm)": 14.7,
"Flipper Length (mm)": 210,
"Body Mass (g)": 4700,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 53.4,
"Beak Depth (mm)": 15.8,
"Flipper Length (mm)": 219,
"Body Mass (g)": 5500,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 43.3,
"Beak Depth (mm)": 14,
"Flipper Length (mm)": 208,
"Body Mass (g)": 4575,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 48.1,
"Beak Depth (mm)": 15.1,
"Flipper Length (mm)": 209,
"Body Mass (g)": 5500,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 50.5,
"Beak Depth (mm)": 15.2,
"Flipper Length (mm)": 216,
"Body Mass (g)": 5000,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 49.8,
"Beak Depth (mm)": 15.9,
"Flipper Length (mm)": 229,
"Body Mass (g)": 5950,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 43.5,
"Beak Depth (mm)": 15.2,
"Flipper Length (mm)": 213,
"Body Mass (g)": 4650,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 51.5,
"Beak Depth (mm)": 16.3,
"Flipper Length (mm)": 230,
"Body Mass (g)": 5500,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.2,
"Beak Depth (mm)": 14.1,
"Flipper Length (mm)": 217,
"Body Mass (g)": 4375,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 55.1,
"Beak Depth (mm)": 16,
"Flipper Length (mm)": 230,
"Body Mass (g)": 5850,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 44.5,
"Beak Depth (mm)": 15.7,
"Flipper Length (mm)": 217,
"Body Mass (g)": 4875,
"Sex": "."
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 48.8,
"Beak Depth (mm)": 16.2,
"Flipper Length (mm)": 222,
"Body Mass (g)": 6000,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 47.2,
"Beak Depth (mm)": 13.7,
"Flipper Length (mm)": 214,
"Body Mass (g)": 4925,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": null,
"Beak Depth (mm)": null,
"Flipper Length (mm)": null,
"Body Mass (g)": null,
"Sex": null
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 46.8,
"Beak Depth (mm)": 14.3,
"Flipper Length (mm)": 215,
"Body Mass (g)": 4850,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 50.4,
"Beak Depth (mm)": 15.7,
"Flipper Length (mm)": 222,
"Body Mass (g)": 5750,
"Sex": "MALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 45.2,
"Beak Depth (mm)": 14.8,
"Flipper Length (mm)": 212,
"Body Mass (g)": 5200,
"Sex": "FEMALE"
},
{
"Species": "Gentoo",
"Island": "Biscoe",
"Beak Length (mm)": 49.9,
"Beak Depth (mm)": 16.1,
"Flipper Length (mm)": 213,
"Body Mass (g)": 5400,
"Sex": "MALE"
}
]
================================================
FILE: contrib/clickhouse/podman-config
================================================
NAME=clickhouse
IMAGE=docker.io/clickhouse/clickhouse-server
PUBLISH=9000:9000
ENV="CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 CLICKHOUSE_USER=clickhouse CLICKHOUSE_PASSWORD=P4ssw0rd"
================================================
FILE: contrib/clickhouse/usql-config
================================================
DB="clickhouse://clickhouse:P4ssw0rd@localhost"
VSQL="select version() as version;"
================================================
FILE: contrib/cockroach/podman-config
================================================
NAME=cockroach
IMAGE=docker.io/cockroachdb/cockroach:latest
PUBLISH=26257:26257
ENV="COCKROACH_DATABASE=cockroach COCKROACH_USER=cockroach COCKROACH_PASSWORD=P4ssw0rd"
CMD=start-single-node
================================================
FILE: contrib/config.yaml
================================================
---
# named connections
connections:
my_couchbase_conn: couchbase://Administrator:P4ssw0rd@localhost
my_clickhouse_conn: clickhouse://clickhouse:P4ssw0rd@localhost
css: cassandra://cassandra:cassandra@localhost
fsl: flightsql://flight_username:P4ssw0rd@localhost
gdr:
protocol: godror
username: system
password: P4ssw0rd
hostname: localhost
port: 1521
database: free
ign: ignite://ignite:ignite@localhost
mss: sqlserver://sa:Adm1nP@ssw0rd@localhost
mym: mysql://root:P4ssw0rd@localhost
myz: mymysql://root:P4ssw0rd@localhost
ora: oracle://system:P4ssw0rd@localhost/free
ore: oracle://system:P4ssw0rd@localhost:1522/db1
pgs: postgres://postgres:P4ssw0rd@localhost
pgx: pgx://postgres:P4ssw0rd@localhost
vrt:
proto: vertica
user: vertica
pass: vertica
host: localhost
sll:
file: /path/to/mydb.sqlite3
mdc: modernsqlite:test.db
dkd: test.duckdb
zzz: ["databricks", "token:dapi*****@adb-*************.azuredatabricks.net:443/sql/protocolv1/o/*********/*******"]
zz2:
proto: mysql
user: "my username"
pass: "my password!"
host: localhost
opts:
opt1: "😀"
# init script
init: |
\echo welcome to the jungle `date`
\set SYNTAX_HL_STYLE paraiso-dark
\set PROMPT1 '\033[32m%S%M%/%R%#\033[0m '
\set bar test
\set foo test
-- \set SHOW_HOST_INFORMATION false
-- \set SYNTAX_HL false
\set 型示師 '本門台初埼本門台初埼'
# charts path
charts_path: charts
# defined queries
queries:
q1:
================================================
FILE: contrib/couchbase/README.md
================================================
# Couchbase Notes
```sh
$ podman volume create couchbase-data
```
After running the docker image, browse to http://127.0.0.1:8091/ui/index.html
and manually configure database.
================================================
FILE: contrib/couchbase/podman-config
================================================
NAME=couchbase
IMAGE=docker.io/library/couchbase
PUBLISH=8091-8094:8091-8094
VOLUME=couchbase-data:/opt/couchbase/var
================================================
FILE: contrib/couchbase/usql-config
================================================
# NOTE: this will only work after setting up a database on http://localhost:8091/
DB="couchbase://Administrator:P4ssw0rd@localhost"
VSQL="select raw ds_version() as version;"
================================================
FILE: contrib/db2/README.md
================================================
# db2 Notes
1. Install unixodbc:
```sh
$ sudo aptitude install unixodbc unixodbc-bin unixodbc-dev
$ yay -S unixodbc
```
2. Download `dsdriver` and install:
```sh
$ ls ~/Downloads/ibm_data_server_driver_package_linuxx64_v11.5.tar.gz
/home/ken/Downloads/ibm_data_server_driver_package_linuxx64_v11.5.tar.gz
$ sudo ./install-dsdriver.sh
```
3. Copy ODBC and CLI configs:
```sh
$ cat odbcinst.ini | sudo tee -a /etc/odbcinst.ini
$ sudo cp {db2cli.ini,db2dsdriver.cfg} /opt/db2/clidriver/cfg/
```
4. Run DB2 container:
```sh
$ ../podman-run.sh db2 -u
```
5. Verify DB2 working:
```sh
$ ./db2cli-validate.sh
```
================================================
FILE: contrib/db2/db2cli-validate.sh
================================================
#!/bin/bash
# see https://www.ibm.com/developerworks/community/blogs/ff78a96f-bf23-457e-befa-77f266844cbb/entry/db2cli_validate_command_line_tool_for_validating_and_testing_cli_environment_and_configuration?lang=en
# see https://web.archive.org/web/20240715121603/https://blogs.sas.com/content/sgf/2017/11/16/connecting-sas-db2-database-via-odbc-without-tears/
CLIDRIVER=${1:-/opt/db2/clidriver}
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$CLIDRIVER/lib
export DB2CLIINIPATH=$CLIDRIVER/cfg
export DB2DSDRIVER_CFG_PATH=$CLIDRIVER/cfg
$CLIDRIVER/bin/db2cli validate -dsn SAMPLE -connect -user db2inst1 -passwd P4ssw0rd
================================================
FILE: contrib/db2/db2dsdriver.cfg
================================================
================================================
FILE: contrib/db2/install-dsdriver.sh
================================================
#!/bin/bash
DEST=${1:-/opt/db2}
FILE=$2
if [ ! -w $DEST ]; then
echo "ERROR: not able to write to $DEST"
exit 1
fi
echo "DEST: $DEST"
if [ ! -e "$DEST" ]; then
echo "$DEST does not exist"
exit 1
fi
if [ -z "$FILE" ]; then
FILE=$(ls $HOME/Downloads/ibm_data_server_driver_package_linuxx64_*.tar.gz||:)
fi
if [ -z "$FILE" ]; then
echo "cannot find driver package to extract"
exit 1
fi
set -e
USER=$(whoami)
# extract
pushd $DEST &> /dev/null
echo "EXTRACTING: $FILE"
# extract
tar -zxf $FILE
tar -zxf dsdriver/odbc_cli_driver/linuxamd64/ibm_data_server_driver_for_odbc_cli.tar.gz
# fix permissions
chown $USER:$USER -R .
find ./ -type d -exec chmod 0755 {} \;
find ./ -type d -exec chmod -s {} \;
popd &> /dev/null
================================================
FILE: contrib/db2/podman-config
================================================
NAME=db2
IMAGE=icr.io/db2_community/db2
PUBLISH="50000:50000 55000:55000"
ENV="LICENSE=accept DB2INSTANCE=db2inst1 DB2INST1_PASSWORD=P4ssw0rd DBNAME=testdb"
VOLUME=db2-data:/database
================================================
FILE: contrib/db2/test.sql
================================================
\connect odbc+db2://db2inst1:P4ssw0rd@localhost/testdb
create schema test;
create table test.mytable (
COL1 INTEGER NOT NULL,
COL2 CHAR(25),
COL3 VARCHAR(25) NOT NULL,
COL4 DATE,
COL5 DECIMAL(10,2),
PRIMARY KEY (COL1),
UNIQUE (COL3)
);
insert into test.mytable
(col1, col2, col3, col4, col5)
values
(1, 'a', 'first', current date, 15.0),
(2, 'b', 'second', current date, 16.0),
(3, 'c', 'third', current date, 17.0)
;
select * from test.mytable;
================================================
FILE: contrib/db2/usql-config
================================================
DB="odbc+db2://db2inst1:P4ssw0rd@localhost/testdb"
VSQL="SELECT service_level AS version FROM sysibmadm.env_inst_info;"
================================================
FILE: contrib/duckdb/usql-config
================================================
DB="duckdb:test.duckdb"
VSQL="SELECT library_version AS version FROM pragma_version();"
================================================
FILE: contrib/exasol/podman-config
================================================
NAME=exasol
IMAGE=docker.io/exasol/docker-db
PUBLISH=8563:8563
================================================
FILE: contrib/exasol/usql-config
================================================
DB="exasol://sys:exasol@localhost/?encryption=0"
VSQL="SELECT param_value AS version FROM exa_metadata WHERE param_name = 'databaseProductVersion';"
================================================
FILE: contrib/firebird/podman-config
================================================
NAME=firebird
IMAGE=docker.io/jacobalberty/firebird
PUBLISH=3050:3050
ENV="FIREBIRD_DATABASE=booktest FIREBIRD_USER=booktest FIREBIRD_PASSWORD=booktest"
================================================
FILE: contrib/firebird/usql-config
================================================
DB="firebird://booktest:booktest@localhost/booktest"
VSQL="SELECT rdb\$get_context('SYSTEM', 'ENGINE_VERSION') AS version FROM rdb\$database;"
================================================
FILE: contrib/flightsql/podman-config
================================================
NAME=flightsql
IMAGE=docker.io/voltrondata/flight-sql
PUBLISH=31337:31337
ENV="FLIGHT_PASSWORD=P4ssw0rd"
================================================
FILE: contrib/flightsql/usql-config
================================================
DB="flightsql://flight_username:P4ssw0rd@localhost:31337?tls=skip-verify"
VSQL="SELECT version() AS version;"
================================================
FILE: contrib/go-setup.sh
================================================
#!/bin/bash
# trimmed down version of:
# https://github.com/kenshaw/shell-config/blob/master/scripts/go-setup.sh
ARCH=$(uname -m)
PLATFORM=linux
case $ARCH in
aarch64) ARCH=arm64 ;;
x86_64) ARCH=amd64 ;;
esac
REPO=https://go.googlesource.com/go
DL=https://go.dev/dl/
EXT=tar.gz
DEST=/usr/local
set -e
LATEST=$(curl -4 -s "$DL"|sed -E -n "/go1\.[0-9]+(\.[0-9]+)?\.$PLATFORM-$ARCH\.$EXT(.+?)<\/a.*/\1/' <<< "$LATEST")
STABLE=$(sed -E -e 's/^go//' -e "s/\.$PLATFORM-$ARCH\.$EXT$//" <<< "$ARCHIVE")
if ! [[ "$STABLE" =~ ^1\.[0-9\.]+$ ]]; then
echo "ERROR: unable to retrieve latest Go version for $PLATFORM/$ARCH ($STABLE)"
exit 1
fi
REMOTE=$(sed -E -e 's/.* $2"
curl -4 -L -# -o $2 $1
}
# extract
WORKDIR=$(mktemp -d /tmp/go-setup.XXXX)
grab $REMOTE $WORKDIR/$ARCHIVE
echo "USING: $WORKDIR/$ARCHIVE"
pushd $WORKDIR &> /dev/null
case $EXT in
tar.gz) tar -zxf $ARCHIVE ;;
zip) unzip -q $ARCHIVE ;;
*)
echo "ERROR: unknown extension $EXT"
exit
;;
esac
echo "MOVING: $WORKDIR/go -> $DEST/go"
mv go $DEST/go
chown -R root:root $DEST/go
echo "INSTALLED: $($DEST/go/bin/go version)"
================================================
FILE: contrib/godror/fix-oob-config.sh
================================================
#!/bin/bash
# adds DISABLE_OOB=on to user's .sqlnet.ora config
#
# See:
# https://github.com/oracle/docker-images/issues/1352
# https://franckpachot.medium.com/19c-instant-client-and-docker-1566630ab20e
echo "DISABLE_OOB=ON" >> $HOME/.sqlnet.ora
================================================
FILE: contrib/godror/grab-instantclient.sh
================================================
#!/bin/bash
DEST=${1:-/opt/oracle}
# available versions:
# 21.7.0.0.0
# 21.6.0.0.0
# 21.1.0.0.0
# 19.9.0.0.0
# 18.5.0.0.0
# 12.2.0.1.0
VERSION=
OPTIND=1
while getopts "v:" opt; do
case "$opt" in
v) VERSION=$OPTARG ;;
esac
done
if [ -z "$VERSION" ]; then
VERSION=$(
wget --quiet -O- https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html| \
sed -n -e 's/.*\/instantclient-basic-linux\.x64-\([^d]\+\)dbru\.zip.*/\1/p' | \
head -1
)
fi
if [[ ! "$VERSION" =~ ^[0-9\.]+$ ]]; then
echo "error: invalid VERSION"
exit 1
fi
BASE=https://download.oracle.com/otn_software/linux/instantclient/$(sed -e 's/[^0-9]//g' <<< "$VERSION")
# build list of archives to retrieve
declare -a ARCHIVES
for i in basic sdk sqlplus; do
ARCHIVES+=("$BASE/instantclient-$i-linux.x64-${VERSION}dbru.zip")
done
grab() {
echo -n "RETRIEVING: $1 -> $2 "
wget --progress=dot -O $2 $1 2>&1 |\
grep --line-buffered "%" | \
sed -u -e "s,\.,,g" | \
awk '{printf("\b\b\b\b%4s", $2)}'
echo -ne "\b\b\b\b"
echo " DONE."
}
cache() {
FILE=$(basename $2)
if [ ! -f $1/$FILE ]; then
grab $2 $1/$FILE
fi
}
set -e
echo "DEST: $DEST"
if [ ! -w $DEST ]; then
echo "$DEST is not writable!"
exit 1
fi
if [ ! -e "$DEST" ]; then
echo "$DEST does not exist!"
exit 1
fi
# retrieve archives
for i in ${ARCHIVES[@]}; do
cache $DEST $i
done
# remove existing directory, if any
DVER=$(awk -F. '{print $1 "_" $2}' <<< "$VERSION")
if [ -e $DEST/instantclient_$DVER ]; then
echo "REMOVING: $DEST/instantclient_$DVER"
rm -rf $DEST/instantclient_$DVER
fi
# extract
pushd $DEST &> /dev/null
for i in ${ARCHIVES[@]}; do
unzip -qq $(basename $i)
done
popd &> /dev/null
# write pkg-config file
DATA=$(cat < $DEST/oci8.pc
rm -f /etc/ld.so.conf.d/oracle-instantclient.conf
echo "$DEST/instantclient_$DVER" | tee -a /etc/ld.so.conf.d/oracle-instantclient.conf
ldconfig -v
# write sqlnet.ora
DATA=$(cat < $DEST/instantclient_${DVER}/network/admin/sqlnet.ora
================================================
FILE: contrib/godror/usql-config
================================================
DB="godror://system:P4ssw0rd@localhost/orasid"
VSQL="SELECT version FROM v\$instance"
================================================
FILE: contrib/h2/podman-config
================================================
NAME=h2
IMAGE=docker.io/buildo/h2database
PUBLISH="8082:8082 9092:9092"
================================================
FILE: contrib/hive/podman-config
================================================
NAME=hive
IMAGE=docker.io/apache/hive:4.0.0-beta-1
PUBLISH="10000:10000 10002:10002"
ENV="SERVICE_NAME=hiveserver2"
================================================
FILE: contrib/hive/usql-config
================================================
DB="hive://user:pass@localhost"
VSQL="SELECT version() AS version;"
================================================
FILE: contrib/ignite/README.md
================================================
# Ignite Notes
After starting the database, run `activate.sh`:
```sh
$ ./activate.sh
```
================================================
FILE: contrib/ignite/activate.sh
================================================
#!/bin/bash
docker exec -it ignite \
/opt/ignite/apache-ignite/bin/control.sh \
--activate \
--user ignite \
--password ignite
================================================
FILE: contrib/ignite/podman-config
================================================
NAME=ignite
IMAGE=docker.io/usql/ignite
PUBLISH=10800:10800
NETWORK=host
================================================
FILE: contrib/ignite/usql-config
================================================
DB="ignite://ignite:ignite@localhost/ExampleDB"
================================================
FILE: contrib/mymysql/usql-config
================================================
DB="mymysql://root:P4ssw0rd@localhost/"
VSQL="SELECT version() AS version;"
================================================
FILE: contrib/mysql/podman-config
================================================
NAME=mysql
IMAGE=docker.io/library/mariadb
PUBLISH=3306:3306
ENV="MYSQL_ROOT_PASSWORD=P4ssw0rd"
================================================
FILE: contrib/mysql/test.sql
================================================
-- mysql test script
\set
\set SYNTAX_HL_FORMAT terminal16m
\set SYNTAX_HL true
\?
\copyright
\set SYNTAX_HL_STYLE dracula
select 'test''
' \g
\set NAME myname
drop database if exists testdb; create database testdb; use testdb;
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS authors;
DROP TABLE IF EXISTS books;
DROP FUNCTION IF EXISTS say_hello;
SET FOREIGN_KEY_CHECKS=1;
CREATE TABLE authors (
author_id integer NOT NULL AUTO_INCREMENT PRIMARY KEY,
name text NOT NULL DEFAULT ''
) ENGINE=InnoDB;
CREATE INDEX authors_name_idx ON authors(name(255));
\set SYNTAX_HL_STYLE paraiso-dark
CREATE TABLE books (
/*
this is a multiline comment
*/
book_id integer NOT NULL AUTO_INCREMENT PRIMARY KEY,
author_id integer NOT NULL,
isbn varchar(255) NOT NULL DEFAULT '' UNIQUE,
book_type ENUM('FICTION', 'NONFICTION') NOT NULL DEFAULT 'FICTION',
title text NOT NULL DEFAULT '',
year integer NOT NULL DEFAULT 2000,
available datetime NOT NULL DEFAULT NOW(),
tags text NOT NULL DEFAULT '',
CONSTRAINT FOREIGN KEY (author_id) REFERENCES authors(author_id)
) ENGINE=InnoDB;
CREATE INDEX books_title_idx ON books(title, year);
insert into authors (name) values
('jk rowling'),
('author amazing')
\g
select * from authors;
\set COLNAME name
\set NAME amaz
\echo `echo hello`
select :"COLNAME" from authors where :COLNAME like '%' || :'NAME' || '%'
\print \raw
\g
\gset AUTHOR_
select :'AUTHOR_name';
\begin
insert into authors (name) values ('test');
\rollback
insert into authors (name) values ('hello');
select * from authors;
insert into books (author_id, isbn, title, year, available) values
(1, '1', 'one', 2018, '2018-06-01 00:00:00'),
(2, '2', 'two', 2019, '2019-06-01 00:00:00')
;
select * from books b inner join authors a on a.author_id = b.author_id;
CREATE FUNCTION say_hello(s text) RETURNS text
DETERMINISTIC
RETURN CONCAT('hello ', s);
select say_hello('a name!') \G
/* exiting! */
\q
================================================
FILE: contrib/mysql/usql-config
================================================
DB="mysql://root:P4ssw0rd@localhost/"
VSQL="SELECT version() AS version;"
================================================
FILE: contrib/oracle/init.sql
================================================
\set ORACLE_USER system
\set ORACLE_PASS oracle
\set ORACLE_SVC xe
\set ORACLE_HOST `docker port oracle 1521`
\prompt NAME 'Create database user: '
\prompt -password PASS 'Password for "':NAME'": '
\connect 'oracle://':ORACLE_USER':':ORACLE_PASS'@':ORACLE_HOST'/':ORACLE_SVC
\set DATNAME :NAME.dat
CREATE
TABLESPACE :NAME
NOLOGGING
DATAFILE :'DATNAME'
SIZE 100m
AUTOEXTEND ON;
CREATE
USER :NAME
IDENTIFIED BY :NAME
DEFAULT TABLESPACE :NAME;
GRANT
CREATE SESSION,
CREATE TABLE,
CREATE VIEW,
CREATE SEQUENCE,
CREATE PROCEDURE,
CREATE TRIGGER,
UNLIMITED TABLESPACE,
SELECT ANY DICTIONARY
TO :NAME;
ALTER SYSTEM
SET OPEN_CURSORS=400
SCOPE=both;
================================================
FILE: contrib/oracle/podman-config
================================================
NAME=oracle
IMAGE=container-registry.oracle.com/database/free
PUBLISH=1521:1521
ENV="ORACLE_PDB=db1 ORACLE_PWD=P4ssw0rd"
VOLUME=oracle-free-data:/opt/oracle/oradata
================================================
FILE: contrib/oracle/usql-config
================================================
DB="oracle://system:P4ssw0rd@localhost/free"
VSQL="SELECT version FROM v\$instance"
================================================
FILE: contrib/oracle-enterprise/podman-config
================================================
NAME=oracle-enterprise
IMAGE=container-registry.oracle.com/database/enterprise:21.3.0.0
PUBLISH=1522:1521
ENV="ORACLE_PDB=db1 ORACLE_PWD=P4ssw0rd"
VOLUME=oracle-enterprise-data:/opt/oracle/oradata
================================================
FILE: contrib/oracle-enterprise/usql-config
================================================
DB="oracle://system:P4ssw0rd@localhost:1522/db1"
VSQL="SELECT version FROM v\$instance"
================================================
FILE: contrib/pgx/usql-config
================================================
DB="pgx://postgres:P4ssw0rd@localhost"
VSQL="SELECT setting AS version FROM pg_settings WHERE name='server_version';"
================================================
FILE: contrib/podman-run.sh
================================================
#!/bin/bash
# podman-run.sh: starts or restarts podman containers.
#
# Usage: podman-run.sh [-u]
#
# Where is a name of a subdirectory containing podman-config,
# 'all', or 'test'.
#
# all -- starts all available database images.
# test -- starts the primary testing images. The testing images are cassandra, mysql, postgres, sqlserver, and oracle
# -u -- perform podman pull for images prior to start.
#
# Will stop any running podman container prior to starting.
DIR=$1
SRC=$(realpath $(cd -P "$(dirname "${BASH_SOURCE[0]}" )" && pwd))
if [ -z "$DIR" ]; then
echo "usage: $0 [-u]"
exit 1
fi
shift
UPDATE=0
OPTIND=1
while getopts "u" opt; do
case "$opt" in
u) UPDATE=1 ;;
esac
done
podman_run() {
TARGET=$1
BASE=$SRC/$TARGET
if [ ! -e $BASE/podman-config ]; then
echo "error: $BASE/podman-config doesn't exist"
exit 1
fi
# load parameters from podman-config
unset IMAGE NAME PUBLISH ENV VOLUME NETWORK PRIVILEGED HOSTNAME PARAMS CMD
source $BASE/podman-config
if [[ "$TARGET" != "$NAME" ]]; then
echo "error: $BASE/podman-config is invalid"
exit 1
fi
# default network settings
if [ -z "$NETWORK" ]; then
NETWORK=slirp4netns
fi
# setup params
PARAMS=()
for k in NAME PUBLISH ENV VOLUME NETWORK PRIVILEGED HOSTNAME; do
n=$(tr 'A-Z' 'a-z' <<< "$k")
v=$(eval echo "\$$k")
if [ ! -z "$v" ]; then
for p in $v; do
PARAMS+=("--$n=$p")
done
fi
done
# determine if image exists
EXISTS=$(podman image ls -q $IMAGE)
if [[ "$UPDATE" == "0" && -z "$EXISTS" ]]; then
UPDATE=1
fi
# show parameters
echo "-------------------------------------------"
echo "NAME: $NAME"
echo "IMAGE: $IMAGE (update: $UPDATE)"
echo "PUBLISH: $PUBLISH"
echo "ENV: $ENV"
echo "VOLUME: $VOLUME"
echo "NETWORK: $NETWORK"
echo "PRIVILEGED: $PRIVILEGED"
echo "HOSTNAME: $HOSTNAME"
echo "CMD: $CMD"
echo
# update
if [[ "$UPDATE" == "1" ]]; then
if [ ! -f $BASE/Dockerfile ]; then
(set -ex;
podman pull $IMAGE
)
else
pushd $BASE &> /dev/null
(set -ex;
podman build --pull -t $IMAGE:latest .
)
popd &> /dev/null
fi
REF=$(awk -F: '{print $1}' <<< "$IMAGE")
REMOVE=$(podman image list --filter=dangling=true --filter=reference=$IMAGE -q)
if [ ! -z "$REMOVE" ]; then
(set -ex;
podman image rm -f $REMOVE
)
fi
fi
# stop and remove
if [ ! -z "$(podman ps -q --filter "name=$NAME")" ]; then
(set -x;
podman stop $NAME
)
fi
if [ ! -z "$(podman ps -q -a --filter "name=$NAME")" ]; then
(set -x;
podman rm -f $NAME
)
fi
# start
(set -ex;
podman run --detach --rm ${PARAMS[@]} $IMAGE $CMD
)
echo
}
pushd $SRC &> /dev/null
TARGETS=()
case $DIR in
all)
TARGETS+=($(find . -type f -name podman-config|awk -F'/' '{print $2}'))
;;
test)
TARGETS+=(mysql postgres sqlserver oracle clickhouse cassandra)
;;
*)
TARGETS+=($DIR)
;;
esac
for TARGET in ${TARGETS[@]}; do
podman_run $TARGET
done
popd &> /dev/null
================================================
FILE: contrib/podman-stop.sh
================================================
#!/bin/bash
SRC=$(realpath $(cd -P "$(dirname "${BASH_SOURCE[0]}" )" && pwd))
for TARGET in $SRC/*/podman-config; do
NAME=$(basename $(dirname $TARGET))
if [ ! -z "$(podman ps -q --filter "name=$NAME")" ]; then
(set -x;
podman stop $NAME
)
fi
if [ ! -z "$(podman ps -q -a --filter "name=$NAME")" ]; then
(set -x;
podman rm -f $NAME
)
fi
done
================================================
FILE: contrib/postgres/init.sql
================================================
\set POSTGRES_USER postgres
\set POSTGRES_PASS P4ssw0rd
\set POSTGRES_DB postgres
\set POSTGRES_HOST `docker port postgres 5432 | head -n1`
\prompt NAME 'Create database user: '
\prompt -password PASS 'Password for "':NAME'": '
\connect 'postgres://':POSTGRES_USER':':POSTGRES_PASS'@':POSTGRES_HOST'/':POSTGRES_DB'?sslmode=disable'
DROP USER IF EXISTS :NAME;
CREATE USER :NAME PASSWORD :'PASS';
DROP DATABASE IF EXISTS :NAME;
CREATE DATABASE :NAME OWNER :NAME;
================================================
FILE: contrib/postgres/podman-config
================================================
NAME=postgres
IMAGE=docker.io/usql/postgres
PUBLISH=5432:5432
ENV="POSTGRES_PASSWORD=P4ssw0rd"
================================================
FILE: contrib/postgres/schema.sql
================================================
\connect postgres://booktest:booktest@localhost/
DROP TABLE IF EXISTS books CASCADE;
DROP TYPE IF EXISTS book_type CASCADE;
DROP TABLE IF EXISTS authors CASCADE;
DROP FUNCTION IF EXISTS say_hello(text) CASCADE;
CREATE TABLE authors (
author_id SERIAL PRIMARY KEY,
name text NOT NULL DEFAULT ''
);
CREATE INDEX authors_name_idx ON authors(name);
CREATE TYPE book_type AS ENUM (
'FICTION',
'NONFICTION'
);
CREATE TABLE books (
book_id SERIAL PRIMARY KEY,
author_id integer NOT NULL REFERENCES authors(author_id),
isbn text NOT NULL DEFAULT '' UNIQUE,
booktype book_type NOT NULL DEFAULT 'FICTION',
title text NOT NULL DEFAULT '',
year integer NOT NULL DEFAULT 2000,
available timestamp with time zone NOT NULL DEFAULT 'NOW()',
tags varchar[] NOT NULL DEFAULT '{}'
);
CREATE INDEX books_title_idx ON books(title, year);
CREATE FUNCTION say_hello(text) RETURNS text AS $$
BEGIN
RETURN CONCAT('hello ', $1);
END;
$$ LANGUAGE plpgsql;
CREATE INDEX books_title_lower_idx ON books(title);
================================================
FILE: contrib/postgres/test.sql
================================================
-- postgres test script
\set
\set SYNTAX_HL_FORMAT terminal16m
\set SYNTAX_HL true
\?
\copyright
\set SYNTAX_HL_STYLE dracula
select 'test''
' \g
\set NAME myname
DROP TABLE IF EXISTS books;
DROP TABLE IF EXISTS authors;
DROP TABLE IF EXISTS books CASCADE;
DROP TYPE IF EXISTS book_type CASCADE;
DROP TABLE IF EXISTS authors CASCADE;
DROP FUNCTION IF EXISTS say_hello(text) CASCADE;
CREATE TABLE authors (
author_id SERIAL PRIMARY KEY,
name text NOT NULL DEFAULT ''
);
CREATE INDEX authors_name_idx ON authors(name);
CREATE TYPE book_type AS ENUM (
'FICTION',
'NONFICTION'
);
CREATE INDEX authors_name_idx ON authors(name);
\set SYNTAX_HL_STYLE paraiso-dark
CREATE TABLE books (
/*
this is a multiline comment
*/
book_id SERIAL PRIMARY KEY,
author_id integer NOT NULL REFERENCES authors(author_id),
isbn text NOT NULL DEFAULT '' UNIQUE,
booktype book_type NOT NULL DEFAULT 'FICTION',
title text NOT NULL DEFAULT '',
year integer NOT NULL DEFAULT 2000,
available timestamp with time zone NOT NULL DEFAULT 'NOW()',
tags varchar[] NOT NULL DEFAULT '{}'
);
CREATE INDEX books_title_idx ON books(title, year);
insert into authors (name) values
('jk rowling'),
('author amazing')
\g
select * from authors;
\set COLNAME name
\set NAME amaz
\echo `echo hello`
select :"COLNAME" from authors where :COLNAME like '%' || :'NAME' || '%'
\print \raw
\g
\gset AUTHOR_
select :'AUTHOR_name';
\begin
insert into authors (name) values ('test');
\rollback
insert into authors (name) values ('hello');
select * from authors;
insert into books (author_id, isbn, title, year, available) values
(1, '1', 'one', 2018, '2018-06-01 00:00:00'),
(2, '2', 'two', 2019, '2019-06-01 00:00:00')
;
select * from books b inner join authors a on a.author_id = b.author_id;
CREATE FUNCTION say_hello(text) RETURNS text AS $$
BEGIN
RETURN CONCAT('hello ', $1);
END;
$$ LANGUAGE plpgsql;
select say_hello('a name!') \G
/* exiting! */
\q
================================================
FILE: contrib/postgres/usql-config
================================================
DB="postgres://postgres:P4ssw0rd@localhost"
VSQL="SELECT setting AS version FROM pg_settings WHERE name='server_version';"
================================================
FILE: contrib/presto/podman-config
================================================
NAME=presto
IMAGE=docker.io/ahanaio/prestodb-sandbox
PUBLISH=8080:8080
================================================
FILE: contrib/presto/usql-config
================================================
DB="presto://localhost"
VSQL="SELECT node_version AS version FROM system.runtime.nodes LIMIT 1;"
================================================
FILE: contrib/sqlite3/build-windows-icu.sh
================================================
#!/bin/bash
../source/runConfigureICU \
MinGW \
--host=x86_64-w64-mingw32 \
--disable-release \
--disable-debug \
--enable-static \
--prefix=/opt/local
================================================
FILE: contrib/sqlite3/icu-i18n-mingw64.pc
================================================
mingw64_prefix=C:\msys64\opt\local
includedir="${mingw64_prefix}\include"
libdir="${mingw64_prefix}\lib"
Name: icu-i18n-mingw64
Version: dev
Description: icu-i18n
Cflags: -I${includedir}
Libs: -L${libdir}
================================================
FILE: contrib/sqlite3/test.sql
================================================
-- sqlite3 test script
\set
\set SYNTAX_HL_FORMAT terminal16m
\set SYNTAX_HL true
help
\?
\copyright
\set SYNTAX_HL_STYLE dracula
select 'test''
' \g
\set NAME myname
PRAGMA foreign_keys = 1;
DROP TABLE IF EXISTS books;
DROP TABLE IF EXISTS authors;
CREATE TABLE authors (
author_id integer NOT NULL PRIMARY KEY AUTOINCREMENT,
name text NOT NULL DEFAULT ''
);
CREATE INDEX authors_name_idx ON authors(name);
\set SYNTAX_HL_STYLE paraiso-dark
CREATE TABLE books (
/*
this is a multiline comment
*/
book_id integer NOT NULL PRIMARY KEY AUTOINCREMENT, -- the id of the author
author_id integer NOT NULL REFERENCES authors(author_id),
isbn text NOT NULL DEFAULT '' UNIQUE,
title text NOT NULL DEFAULT '',
year integer NOT NULL DEFAULT 2000,
available timestamp with time zone NOT NULL DEFAULT '',
tags text NOT NULL DEFAULT '{}'
);
CREATE INDEX books_title_idx ON books(title, year);
insert into authors (name) values
("jk rowling"),
("author amazing")
\g
select * from authors;
\set COLNAME name
\set NAME amaz
\echo `echo hello`
select :"COLNAME" from authors where :COLNAME like '%' || :'NAME' || '%'
\print \raw
\g
\gset AUTHOR_
select :'AUTHOR_name';
\begin
insert into authors (name) values ('test');
\rollback
insert into authors (name) values ('hello');
select * from authors;
insert into books (author_id, isbn, title, year, available) values
(1, '1', 'one', 2018, '2018-06-01 00:00:00'),
(2, '2', 'two', 2019, '2019-06-01 00:00:00')
;
select * from books b inner join authors a on a.author_id = b.author_id;
/* exiting! */
\q
================================================
FILE: contrib/sqlite3/usql-config
================================================
DB="sqlite3:test.sqlite3"
VSQL="SELECT sqlite_version() AS version;"
================================================
FILE: contrib/sqlserver/init.sql
================================================
EXEC sp_configure
'contained database authentication', 1;
RECONFIGURE;
DROP LOGIN :NAME;
DROP DATABASE :NAME;
CREATE DATABASE :NAME
CONTAINMENT=PARTIAL;
\set QNAME "''":NAME"''"
\set SQL 'CREATE LOGIN ':NAME' WITH PASSWORD=':QNAME', CHECK_POLICY=OFF, DEFAULT_DATABASE=':NAME';'
EXEC [:NAME].[dbo].[sp_executesql] N:'SQL'
\set SQL 'CREATE USER ':NAME' FOR LOGIN ':NAME' WITH DEFAULT_SCHEMA=':NAME';'
EXEC [:NAME].[dbo].[sp_executesql] N:'SQL';
\set SQL 'CREATE SCHEMA ':NAME' AUTHORIZATION ':NAME';'
EXEC [:NAME].[dbo].[sp_executesql] N:'SQL';
\set SQL 'EXEC sp_addrolemember db_owner, ':QNAME';'
EXEC [:NAME].[dbo].[sp_executesql] N:'SQL';
-- original reconnect version:
--
--\connect 'sqlserver://localhost/':NAME
--
--CREATE LOGIN :NAME
-- WITH
-- PASSWORD=:'PASS',
-- CHECK_POLICY=OFF,
-- DEFAULT_DATABASE=:NAME;
--
--CREATE USER :NAME
-- FOR LOGIN :NAME
-- WITH DEFAULT_SCHEMA=:NAME;
--
--CREATE SCHEMA :NAME AUTHORIZATION :NAME;
--
--EXEC sp_addrolemember 'db_owner', :'NAME';
================================================
FILE: contrib/sqlserver/podman-config
================================================
NAME=sqlserver
IMAGE=mcr.microsoft.com/mssql/server:2022-latest
PUBLISH=1433:1433
ENV="ACCEPT_EULA=Y MSSQL_PID=Express SA_PASSWORD=Adm1nP@ssw0rd"
================================================
FILE: contrib/sqlserver/test.sql
================================================
-- sqlserver test script
\set
\set SYNTAX_HL_FORMAT terminal16m
\set SYNTAX_HL true
\?
\copyright
\set SYNTAX_HL_STYLE dracula
select 'test''
' \g
\set NAME myname
DROP TABLE IF EXISTS books;
DROP TABLE IF EXISTS authors;
CREATE TABLE authors (
author_id integer NOT NULL IDENTITY(1,1) PRIMARY KEY,
name varchar(255) NOT NULL DEFAULT ''
);
CREATE INDEX authors_name_idx ON authors(name);
CREATE TABLE books (
book_id integer NOT NULL IDENTITY(1,1) PRIMARY KEY,
author_id integer NOT NULL FOREIGN KEY REFERENCES authors(author_id),
isbn varchar(255) NOT NULL DEFAULT '' UNIQUE,
title varchar(255) NOT NULL DEFAULT '',
year integer NOT NULL DEFAULT 2000,
available datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
tags varchar(255) NOT NULL DEFAULT ''
);
CREATE INDEX books_title_idx ON books(title, year);
\set SYNTAX_HL_STYLE paraiso-dark
insert into authors (name) values
('jk rowling'),
('author amazing')
\g
select * from authors;
\set COLNAME name
\set NAME amaz
\echo `echo hello`
select :"COLNAME" from authors where :COLNAME like '%' || :'NAME' || '%'
\print \raw
\g
\gset AUTHOR_
select :'AUTHOR_name';
\begin
insert into authors (name) values ('test');
\rollback
insert into authors (name) values ('hello');
select * from authors;
insert into books (author_id, isbn, title, year, available) values
(1, '1', 'one', 2018, '2018-06-01 00:00:00'),
(2, '2', 'two', 2019, '2019-06-01 00:00:00')
;
select * from books b inner join authors a on a.author_id = b.author_id;
/* exiting! */
\q
================================================
FILE: contrib/sqlserver/usql-config
================================================
DB="sqlserver://sa:Adm1nP@ssw0rd@localhost/"
VSQL="SELECT SERVERPROPERTY('productversion') AS version;"
================================================
FILE: contrib/trino/podman-config
================================================
NAME=trino
IMAGE=docker.io/trinodb/trino
PUBLISH=8080:8080
================================================
FILE: contrib/trino/usql-config
================================================
DB="trino://localhost"
VSQL="SELECT node_version AS version FROM system.runtime.nodes LIMIT 1;"
================================================
FILE: contrib/usql-test.sh
================================================
#!/bin/bash
SRC=$(realpath $(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd))
USQL=$(which usql)
if [ -f $SRC/../usql ]; then
USQL=$(realpath $SRC/../usql)
fi
export USQL_SHOW_HOST_INFORMATION=false
for TARGET in $SRC/*/usql-config; do
NAME=$(basename $(dirname $TARGET))
if [[ ! -z "$(podman ps -q --filter "name=$NAME")" || "$NAME" == "duckdb" || "$NAME" == "sqlite3" ]]; then
unset DB VSQL
source $TARGET
if [[ -z "$DB" || -z "$VSQL" ]]; then
echo -e "ERROR: DB or VSQL not defined in $TARGET!\n"
continue
fi
(set -x;
$USQL "$DB" -X -J -c "$VSQL"
)
echo
fi
done
================================================
FILE: contrib/usqlpass
================================================
# sample ~/.usqlpass file
#
# format is:
# protocol:host:port:dbname:user:pass
postgres:*:*:*:postgres:P4ssw0rd
cql:*:*:*:cassandra:cassandra
clickhouse:*:*:*:clickhouse:P4ssw0rd
couchbase:*:*:*:Administrator:P4ssw0rd
godror:*:*:*:system:P4ssw0rd
ignite:*:*:*:ignite:ignite
mymysql:*:*:*:root:P4ssw0rd
mysql:*:*:*:root:P4ssw0rd
oracle:*:*:*:system:P4ssw0rd
pgx:*:*:*:postgres:P4ssw0rd
sqlserver:*:*:*:sa:Adm1nP@ssw0rd
vertica:*:*:*:vertica:P4ssw0rd
flightsql:*:*:*:flight_username:P4ssw0rd
================================================
FILE: contrib/usqlrc
================================================
-- example usqlrc file
-- put in $HOME/.usqlrc
\echo welcome `echo $USER`, today is:`date`
\set SYNTAX_HL_STYLE paraiso-dark
================================================
FILE: contrib/vertica/podman-config
================================================
NAME=vertica
IMAGE=docker.io/vertica/vertica-ce:latest
PUBLISH=5433:5433
ENV="APP_DB_USER=vertica APP_DB_PASSWORD=P4ssw0rd"
================================================
FILE: contrib/vertica/usql-config
================================================
DB="vertica://vertica:P4ssw0rd@localhost/vertica"
VSQL="SELECT version() AS version;"
================================================
FILE: contrib/ydb/podman-config
================================================
NAME=ydb
IMAGE=cr.yandex/yc/yandex-docker-local-ydb
PUBLISH="2135:2135 2136:2136 8765:8765"
ENV="YDB_DEFAULT_LOG_LEVEL=NOTICE GRPC_TLS_PORT=2135 GRPC_PORT=2136 MON_PORT=8765"
VOLUME="ydb-certs:/ydb_certs ydb-data:/ydb_data"
HOSTNAME=localhost
================================================
FILE: contrib/ydb/usql-config
================================================
DB="ydb://localhost/local"
VSQL="SELECT 'unk' as version;"
================================================
FILE: drivers/adodb/adodb.go
================================================
// Package adodb defines and registers usql's Microsoft ADODB driver. Requires
// CGO. Windows only.
//
// Alias: oleodbc, OLE ODBC
//
// See: https://github.com/mattn/go-adodb
package adodb
import (
"database/sql"
"regexp"
"strings"
_ "github.com/mattn/go-adodb" // DRIVER
"github.com/xo/dburl"
"github.com/xo/usql/drivers"
)
func init() {
endRE := regexp.MustCompile(`;?\s*$`)
endAnchorRE := regexp.MustCompile(`(?i)\send\s*;\s*$`)
drivers.Register("adodb", drivers.Driver{
AllowMultilineComments: true,
AllowCComments: true,
Process: func(u *dburl.URL, prefix string, sqlstr string) (string, string, bool, error) {
// trim last ; but only when not END;
if s := strings.ToLower(u.Query().Get("usql_trim")); s != "" && s != "off" && s != "0" && s != "false" {
if !endAnchorRE.MatchString(sqlstr) {
sqlstr = endRE.ReplaceAllString(sqlstr, "")
}
}
typ, q := drivers.QueryExecType(prefix, sqlstr)
return typ, sqlstr, q, nil
},
RowsAffected: func(res sql.Result) (int64, error) {
return 0, nil
},
}, "oleodbc")
}
================================================
FILE: drivers/athena/athena.go
================================================
// Package athena defines and registers usql's AWS Athena driver.
//
// See: https://github.com/uber/athenadriver
package athena
import (
"context"
_ "github.com/uber/athenadriver/go" // DRIVER: awsathena
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("awsathena", drivers.Driver{
AllowMultilineComments: true,
Process: drivers.StripTrailingSemicolon,
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
err := db.QueryRowContext(
ctx,
`SELECT node_version FROM system.runtime.nodes LIMIT 1`,
).Scan(&ver)
if err != nil {
return "", err
}
return "Athena " + ver, nil
},
})
}
================================================
FILE: drivers/avatica/avatica.go
================================================
// Package avatica defines and registers usql's Apache Avatica driver.
//
// See: https://github.com/apache/calcite-avatica-go
package avatica
import (
"strconv"
_ "github.com/apache/calcite-avatica-go/v5" // DRIVER
avaticaerrors "github.com/apache/calcite-avatica-go/v5/errors"
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("avatica", drivers.Driver{
AllowMultilineComments: true,
AllowCComments: true,
Err: func(err error) (string, string) {
if e, ok := err.(avaticaerrors.ResponseError); ok {
return strconv.Itoa(int(e.ErrorCode)), e.ErrorMessage
}
return "", err.Error()
},
})
}
================================================
FILE: drivers/bigquery/bigquery.go
================================================
// Package bigquery defines and registers usql's Google BigQuery driver.
//
// See: https://github.com/go-gorm/bigquery
package bigquery
import (
"github.com/xo/usql/drivers"
_ "gorm.io/driver/bigquery/driver" // DRIVER
)
func init() {
drivers.Register("bigquery", drivers.Driver{})
}
================================================
FILE: drivers/cassandra/cassandra.go
================================================
// Package cassandra defines and registers usql's Cassandra driver.
//
// See: https://github.com/MichaelS11/go-cql-driver
package cassandra
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"io"
"log"
"os"
"regexp"
"strings"
cql "github.com/MichaelS11/go-cql-driver" // DRIVER: cql
"github.com/gocql/gocql"
"github.com/xo/dburl"
"github.com/xo/usql/drivers"
)
func init() {
var debug bool
if s := os.Getenv("CQL_DEBUG"); s != "" {
log.Printf("ENABLING DEBUGGING FOR CQL")
debug = true
}
// error regexp's
authReqRE := regexp.MustCompile(`authentication required`)
passwordErrRE := regexp.MustCompile(`Provided username (.*)and/or password are incorrect`)
var l *logger
drivers.Register("cql", drivers.Driver{
AllowDollar: true,
AllowMultilineComments: true,
AllowCComments: true,
LexerName: "cql",
ForceParams: func(u *dburl.URL) {
if q := u.Query(); q.Get("timeout") == "" {
q.Set("timeout", "300s")
u.RawQuery = q.Encode()
}
},
Open: func(_ context.Context, u *dburl.URL, stdout, stderr func() io.Writer) (func(string, string) (*sql.DB, error), error) {
// override cql and gocql loggers
l = &logger{debug: debug}
gocql.Logger, cql.CqlDriver.Logger = l, log.New(l, "", 0)
return sql.Open, nil
},
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var release, protocol, cql string
err := db.QueryRowContext(
ctx,
`SELECT release_version, cql_version, native_protocol_version FROM system.local WHERE key = 'local'`,
).Scan(&release, &cql, &protocol)
if err != nil {
return "", err
}
return "Cassandra " + release + ", CQL " + cql + ", Protocol v" + protocol, nil
},
ChangePassword: func(db drivers.DB, user, newpw, _ string) error {
_, err := db.Exec(`ALTER ROLE ` + user + ` WITH PASSWORD = '` + newpw + `'`)
return err
},
IsPasswordErr: func(err error) bool {
return passwordErrRE.MatchString(l.last)
},
Err: func(err error) (string, string) {
if authReqRE.MatchString(l.last) {
return "", "authentication required"
}
if m := passwordErrRE.FindStringSubmatch(l.last); m != nil {
return "", fmt.Sprintf("invalid username %sor password", m[1])
}
return "", strings.TrimPrefix(strings.TrimPrefix(err.Error(), "driver: "), "gocql: ")
},
RowsAffected: func(sql.Result) (int64, error) {
return 0, nil
},
ConvertDefault: func(v interface{}) (string, error) {
buf, err := json.Marshal(v)
if err != nil {
return "", err
}
return string(buf), nil
},
BatchQueryPrefixes: map[string]string{
"BEGIN BATCH": "APPLY BATCH",
},
})
}
// logger is a null logger that satisfies the gocql.StdLogger and the io.Writer
// interfaces in order to capture the last error issued by the cql/gocql
// packages, since the cql package does not (at this time) return any error
// other than sql.ErrBadConn.
type logger struct {
debug bool
last string
}
func (l *logger) Print(v ...interface{}) {
if l.debug {
log.Print(v...)
}
}
func (l *logger) Printf(s string, v ...interface{}) {
if l.debug {
log.Printf(s, v...)
}
}
func (l *logger) Println(v ...interface{}) {
if l.debug {
log.Println(v...)
}
}
func (l *logger) Write(buf []byte) (int, error) {
if l.debug {
log.Printf("WRITE: %s", string(buf))
}
l.last = string(buf)
return len(buf), nil
}
================================================
FILE: drivers/chai/chai.go
================================================
// Package chai defines and registers usql's ChaiSQL driver.
//
// See: https://github.com/chaisql/chai
package chai
import (
_ "github.com/chaisql/chai" // DRIVER
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("chai", drivers.Driver{})
}
================================================
FILE: drivers/clickhouse/clickhouse.go
================================================
// Package clickhouse defines and registers usql's ClickHouse driver.
//
// Group: base
// See: https://github.com/ClickHouse/clickhouse-go
package clickhouse
import (
"database/sql"
"strconv"
"strings"
"github.com/ClickHouse/clickhouse-go/v2" // DRIVER
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("clickhouse", drivers.Driver{
AllowMultilineComments: true,
RowsAffected: func(sql.Result) (int64, error) {
return 0, nil
},
ChangePassword: func(db drivers.DB, user, newpw, oldpw string) error {
_, err := db.Exec(`ALTER USER ` + user + ` IDENTIFIED BY '` + newpw + `'`)
return err
},
Err: func(err error) (string, string) {
if e, ok := err.(*clickhouse.Exception); ok {
return strconv.Itoa(int(e.Code)), strings.TrimPrefix(e.Message, "clickhouse: ")
}
return "", err.Error()
},
IsPasswordErr: func(err error) bool {
if e, ok := err.(*clickhouse.Exception); ok {
return e.Code == 516
}
return false
},
Copy: drivers.CopyWithInsert(func(int) string { return "?" }),
NewMetadataReader: NewMetadataReader,
})
}
================================================
FILE: drivers/clickhouse/clickhouse_test.go
================================================
package clickhouse_test
import (
"context"
"database/sql"
"flag"
"fmt"
"github.com/xo/dburl"
"github.com/xo/usql/drivers"
"log"
"os"
"path/filepath"
"testing"
"time"
dt "github.com/ory/dockertest/v3"
"github.com/xo/usql/drivers/clickhouse"
"github.com/xo/usql/drivers/metadata"
"github.com/yookoala/realpath"
_ "github.com/xo/usql/drivers/csvq"
_ "github.com/xo/usql/drivers/moderncsqlite"
)
// db is the database connection.
var db struct {
db *sql.DB
res *dt.Resource
r metadata.BasicReader
}
func TestMain(m *testing.M) {
cleanup := flag.Bool("cleanup", true, "cleanup when finished")
flag.Parse()
code, err := doMain(m, *cleanup)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
if code == 0 {
code = 1
}
}
os.Exit(code)
}
func doMain(m *testing.M, cleanup bool) (int, error) {
dir, err := os.Getwd()
if err != nil {
return 0, err
}
dir, err = realpath.Realpath(dir)
if err != nil {
return 0, err
}
pool, err := dt.NewPool("")
if err != nil {
return 0, fmt.Errorf("could not connect to docker: %w", err)
}
db.res, err = pool.RunWithOptions(&dt.RunOptions{
Repository: "clickhouse/clickhouse-server",
Tag: "22.7",
Mounts: []string{filepath.Join(dir, "testdata") + ":/docker-entrypoint-initdb.d"},
})
if err != nil {
return 0, fmt.Errorf("unable to run: %w", err)
}
if cleanup {
defer func() {
if err := pool.Purge(db.res); err != nil {
fmt.Fprintf(os.Stderr, "error: could not purge resource: %v\n", err)
}
}()
}
// exponential backoff-retry, because the application in the container
// might not be ready to accept connections yet
if err := pool.Retry(func() error {
port := db.res.GetPort("9000/tcp")
var err error
if db.db, err = sql.Open("clickhouse", fmt.Sprintf("clickhouse://127.0.0.1:%s", port)); err != nil {
return err
}
return db.db.Ping()
}); err != nil {
return 0, fmt.Errorf("unable to open database: %w", err)
}
db.r = clickhouse.NewMetadataReader(db.db).(metadata.BasicReader)
code := m.Run()
return code, nil
}
func TestSchemas(t *testing.T) {
res, err := db.r.Schemas(metadata.Filter{WithSystem: true})
if err != nil {
t.Fatalf("could not read schemas: %v", err)
}
checkNames(t, "schema", res, "default", "system", "tutorial", "tutorial_unexpected", "INFORMATION_SCHEMA", "information_schema", "copy_test")
}
func TestTables(t *testing.T) {
res, err := db.r.Tables(metadata.Filter{
Schema: "tutorial",
Types: []string{"BASE TABLE", "TABLE", "VIEW"},
})
if err != nil {
t.Fatalf("could not read tables: %v", err)
}
checkNames(t, "table", res, "hits_v1", "visits_v1")
}
func TestFunctions(t *testing.T) {
r := clickhouse.NewMetadataReader(db.db).(metadata.FunctionReader)
res, err := r.Functions(metadata.Filter{Schema: "tutorial"})
if err != nil {
t.Fatalf("could not read functions: %v", err)
}
checkNames(t, "function", res, funcNames()...)
}
func TestColumns(t *testing.T) {
res, err := db.r.Columns(metadata.Filter{
Schema: "tutorial",
Parent: "hits_v1",
})
if err != nil {
log.Fatalf("could not read columns: %v", err)
}
checkNames(t, "column", res, colNames()...)
}
func TestCopy(t *testing.T) {
// Tests with csvq source DB. That driver doesn't support ScanType()
for _, destTableSpec := range []string{
"copy_test.dest",
"copy_test.dest(StringCol, NumCol)",
"insert into copy_test.dest values(?, ?)",
} {
t.Run("csvq_"+destTableSpec, func(t *testing.T) {
testCopy(t, destTableSpec, "csvq:.")
})
}
// Test with a driver that supports ScanType()
t.Run("sqlite", func(t *testing.T) {
testCopy(t, "copy_test.dest", "moderncsqlite://:memory:")
})
}
func testCopy(t *testing.T, destTableSpec string, sourceDbUrlStr string) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
_, err := db.db.ExecContext(ctx, "truncate table copy_test.dest")
if err != nil {
t.Fatalf("could not truncate copy_test table: %v", err)
}
// Prepare copy destination URL
port := db.res.GetPort("9000/tcp")
dbUrlStr := fmt.Sprintf("clickhouse://127.0.0.1:%s", port)
dbUrl, err := dburl.Parse(dbUrlStr)
if err != nil {
t.Fatalf("could not parse clickhouse url %s: %v", dbUrlStr, err)
}
// Prepare source data
sourceDbUrl, err := dburl.Parse(sourceDbUrlStr)
if err != nil {
t.Fatalf("could not parse source DB url %s: %v", sourceDbUrlStr, err)
}
sourceDb, err := drivers.Open(ctx, sourceDbUrl, nil, nil)
if err != nil {
t.Fatalf("could not open sourceDb: %v", err)
}
defer sourceDb.Close()
rows, err := sourceDb.QueryContext(ctx, "select 'string', 1")
if err != nil {
t.Fatalf("could not retrieve source rows: %v", err)
}
// Do Copy, ignoring copied rows count because clickhouse driver doesn't report RowsAffected
_, err = drivers.Copy(ctx, dbUrl, nil, nil, rows, destTableSpec)
if err != nil {
t.Fatalf("copy failed: %v", err)
}
rows, err = db.db.QueryContext(ctx, "select StringCol, NumCol from copy_test.dest")
if err != nil {
t.Fatalf("failed to query: %v", err)
}
defer rows.Close()
var copiedString string
var copiedNum int
if !rows.Next() {
t.Fatalf("nothing copied")
}
err = rows.Scan(&copiedString, &copiedNum)
if err != nil {
t.Fatalf("could not read copied data: %v", err)
}
if copiedString != "string" || copiedNum != 1 {
t.Fatalf("copied data differs: %s != string, %d != 1", copiedString, copiedNum)
}
}
func checkNames(t *testing.T, typ string, res interface{ Next() bool }, exp ...string) {
n := make(map[string]bool)
for _, s := range exp {
n[s] = true
}
names := make(map[string]bool)
for res.Next() {
name := getName(res)
if _, ok := names[name]; ok {
t.Errorf("already declared %s %q", typ, name)
}
names[name] = true
}
for name := range n {
if _, ok := names[name]; !ok {
t.Errorf("missing %s %q", typ, name)
}
}
for name := range names {
if _, ok := n[name]; !ok {
t.Errorf("unexpected %s %q", typ, name)
}
}
}
func getName(res interface{}) string {
switch x := res.(type) {
case *metadata.SchemaSet:
return x.Get().Schema
case *metadata.TableSet:
return x.Get().Name
case *metadata.FunctionSet:
return x.Get().Name
case *metadata.ColumnSet:
return x.Get().Name
}
panic(fmt.Sprintf("unknown type %T", res))
}
func funcNames() []string {
return []string{
"BIT_AND",
"BIT_OR",
"BIT_XOR",
"CAST",
"CHARACTER_LENGTH",
"CHAR_LENGTH",
"COVAR_POP",
"COVAR_SAMP",
"CRC32",
"CRC32IEEE",
"CRC64",
"DATABASE",
"DATE",
"DAY",
"DAYOFMONTH",
"DAYOFWEEK",
"DAYOFYEAR",
"FQDN",
"FROM_BASE64",
"FROM_UNIXTIME",
"HOUR",
"INET6_ATON",
"INET6_NTOA",
"INET_ATON",
"INET_NTOA",
"IPv4CIDRToRange",
"IPv4NumToString",
"IPv4NumToStringClassC",
"IPv4StringToNum",
"IPv4StringToNumOrDefault",
"IPv4StringToNumOrNull",
"IPv4ToIPv6",
"IPv6CIDRToRange",
"IPv6NumToString",
"IPv6StringToNum",
"IPv6StringToNumOrDefault",
"IPv6StringToNumOrNull",
"JSONExtract",
"JSONExtractArrayRaw",
"JSONExtractBool",
"JSONExtractFloat",
"JSONExtractInt",
"JSONExtractKeys",
"JSONExtractKeysAndValues",
"JSONExtractKeysAndValuesRaw",
"JSONExtractRaw",
"JSONExtractString",
"JSONExtractUInt",
"JSONHas",
"JSONKey",
"JSONLength",
"JSONType",
"JSON_EXISTS",
"JSON_QUERY",
"JSON_VALUE",
"L1Distance",
"L1Norm",
"L1Normalize",
"L2Distance",
"L2Norm",
"L2Normalize",
"L2SquaredDistance",
"L2SquaredNorm",
"LAST_DAY",
"LinfDistance",
"LinfNorm",
"LinfNormalize",
"LpDistance",
"LpNorm",
"LpNormalize",
"MACNumToString",
"MACStringToNum",
"MACStringToOUI",
"MD4",
"MD5",
"MINUTE",
"MONTH",
"QUARTER",
"REGEXP_MATCHES",
"REGEXP_REPLACE",
"SECOND",
"SHA1",
"SHA224",
"SHA256",
"SHA384",
"SHA512",
"STDDEV_POP",
"STDDEV_SAMP",
"SVG",
"TO_BASE64",
"URLHash",
"URLHierarchy",
"URLPathHierarchy",
"UUIDNumToString",
"UUIDStringToNum",
"VAR_POP",
"VAR_SAMP",
"YEAR",
"_CAST",
"__bitBoolMaskAnd",
"__bitBoolMaskOr",
"__bitSwapLastTwo",
"__bitWrapperFunc",
"__getScalar",
"abs",
"accurateCast",
"accurateCastOrDefault",
"accurateCastOrNull",
"accurate_Cast",
"accurate_CastOrNull",
"acos",
"acosh",
"addDays",
"addHours",
"addMicroseconds",
"addMilliseconds",
"addMinutes",
"addMonths",
"addNanoseconds",
"addQuarters",
"addSeconds",
"addWeeks",
"addYears",
"addressToLine",
"addressToLineWithInlines",
"addressToSymbol",
"aes_decrypt_mysql",
"aes_encrypt_mysql",
"aggThrow",
"alphaTokens",
"and",
"any",
"anyHeavy",
"anyLast",
"appendTrailingCharIfAbsent",
"argMax",
"argMin",
"array",
"arrayAUC",
"arrayAll",
"arrayAvg",
"arrayCompact",
"arrayConcat",
"arrayCount",
"arrayCumSum",
"arrayCumSumNonNegative",
"arrayDifference",
"arrayDistinct",
"arrayElement",
"arrayEnumerate",
"arrayEnumerateDense",
"arrayEnumerateDenseRanked",
"arrayEnumerateUniq",
"arrayEnumerateUniqRanked",
"arrayExists",
"arrayFill",
"arrayFilter",
"arrayFirst",
"arrayFirstIndex",
"arrayFirstOrNull",
"arrayFlatten",
"arrayIntersect",
"arrayJoin",
"arrayLast",
"arrayLastIndex",
"arrayLastOrNull",
"arrayMap",
"arrayMax",
"arrayMin",
"arrayPopBack",
"arrayPopFront",
"arrayProduct",
"arrayPushBack",
"arrayPushFront",
"arrayReduce",
"arrayReduceInRanges",
"arrayResize",
"arrayReverse",
"arrayReverseFill",
"arrayReverseSort",
"arrayReverseSplit",
"arraySlice",
"arraySort",
"arraySplit",
"arrayStringConcat",
"arraySum",
"arrayUniq",
"arrayWithConstant",
"arrayZip",
"asin",
"asinh",
"assumeNotNull",
"atan",
"atan2",
"atanh",
"avg",
"avgWeighted",
"bar",
"base58Decode",
"base58Encode",
"base64Decode",
"base64Encode",
"basename",
"bin",
"bitAnd",
"bitCount",
"bitHammingDistance",
"bitNot",
"bitOr",
"bitPositionsToArray",
"bitRotateLeft",
"bitRotateRight",
"bitShiftLeft",
"bitShiftRight",
"bitSlice",
"bitTest",
"bitTestAll",
"bitTestAny",
"bitXor",
"bitmapAnd",
"bitmapAndCardinality",
"bitmapAndnot",
"bitmapAndnotCardinality",
"bitmapBuild",
"bitmapCardinality",
"bitmapContains",
"bitmapHasAll",
"bitmapHasAny",
"bitmapMax",
"bitmapMin",
"bitmapOr",
"bitmapOrCardinality",
"bitmapSubsetInRange",
"bitmapSubsetLimit",
"bitmapToArray",
"bitmapTransform",
"bitmapXor",
"bitmapXorCardinality",
"bitmaskToArray",
"bitmaskToList",
"blockNumber",
"blockSerializedSize",
"blockSize",
"boundingRatio",
"buildId",
"byteSize",
"caseWithExpr",
"caseWithExpression",
"caseWithoutExpr",
"caseWithoutExpression",
"categoricalInformationValue",
"cbrt",
"ceil",
"ceiling",
"char",
"cityHash64",
"coalesce",
"concat",
"concatAssumeInjective",
"connectionId",
"connection_id",
"contingency",
"convertCharset",
"corr",
"corrStable",
"cos",
"cosh",
"cosineDistance",
"count",
"countDigits",
"countEqual",
"countMatches",
"countMatchesCaseInsensitive",
"countSubstrings",
"countSubstringsCaseInsensitive",
"countSubstringsCaseInsensitiveUTF8",
"covarPop",
"covarPopStable",
"covarSamp",
"covarSampStable",
"cramersV",
"cramersVBiasCorrected",
"currentDatabase",
"currentProfiles",
"currentRoles",
"currentUser",
"cutFragment",
"cutIPv6",
"cutQueryString",
"cutQueryStringAndFragment",
"cutToFirstSignificantSubdomain",
"cutToFirstSignificantSubdomainCustom",
"cutToFirstSignificantSubdomainCustomWithWWW",
"cutToFirstSignificantSubdomainWithWWW",
"cutURLParameter",
"cutWWW",
"dateDiff",
"dateName",
"dateTime64ToSnowflake",
"dateTimeToSnowflake",
"dateTrunc",
"date_trunc",
"decodeURLComponent",
"decodeURLFormComponent",
"decodeXMLComponent",
"decrypt",
"defaultProfiles",
"defaultRoles",
"defaultValueOfArgumentType",
"defaultValueOfTypeName",
"degrees",
"deltaSum",
"deltaSumTimestamp",
"demangle",
"dense_rank",
"detectCharset",
"detectLanguage",
"detectLanguageMixed",
"detectLanguageUnknown",
"detectProgrammingLanguage",
"detectTonality",
"dictGet",
"dictGetChildren",
"dictGetDate",
"dictGetDateOrDefault",
"dictGetDateTime",
"dictGetDateTimeOrDefault",
"dictGetDescendants",
"dictGetFloat32",
"dictGetFloat32OrDefault",
"dictGetFloat64",
"dictGetFloat64OrDefault",
"dictGetHierarchy",
"dictGetInt16",
"dictGetInt16OrDefault",
"dictGetInt32",
"dictGetInt32OrDefault",
"dictGetInt64",
"dictGetInt64OrDefault",
"dictGetInt8",
"dictGetInt8OrDefault",
"dictGetOrDefault",
"dictGetOrNull",
"dictGetString",
"dictGetStringOrDefault",
"dictGetUInt16",
"dictGetUInt16OrDefault",
"dictGetUInt32",
"dictGetUInt32OrDefault",
"dictGetUInt64",
"dictGetUInt64OrDefault",
"dictGetUInt8",
"dictGetUInt8OrDefault",
"dictGetUUID",
"dictGetUUIDOrDefault",
"dictHas",
"dictIsIn",
"distanceL1",
"distanceL2",
"distanceL2Squared",
"distanceLinf",
"distanceLp",
"divide",
"domain",
"domainWithoutWWW",
"dotProduct",
"dumpColumnStructure",
"e",
"empty",
"emptyArrayDate",
"emptyArrayDateTime",
"emptyArrayFloat32",
"emptyArrayFloat64",
"emptyArrayInt16",
"emptyArrayInt32",
"emptyArrayInt64",
"emptyArrayInt8",
"emptyArrayString",
"emptyArrayToSingle",
"emptyArrayUInt16",
"emptyArrayUInt32",
"emptyArrayUInt64",
"emptyArrayUInt8",
"enabledProfiles",
"enabledRoles",
"encodeURLComponent",
"encodeURLFormComponent",
"encodeXMLComponent",
"encrypt",
"endsWith",
"entropy",
"equals",
"erf",
"erfc",
"errorCodeToName",
"evalMLMethod",
"exp",
"exp10",
"exp2",
"exponentialMovingAverage",
"exponentialTimeDecayedAvg",
"exponentialTimeDecayedCount",
"exponentialTimeDecayedMax",
"exponentialTimeDecayedSum",
"extract",
"extractAll",
"extractAllGroups",
"extractAllGroupsHorizontal",
"extractAllGroupsVertical",
"extractGroups",
"extractTextFromHTML",
"extractURLParameter",
"extractURLParameterNames",
"extractURLParameters",
"farmFingerprint64",
"farmHash64",
"file",
"filesystemAvailable",
"filesystemCapacity",
"filesystemFree",
"finalizeAggregation",
"firstSignificantSubdomain",
"firstSignificantSubdomainCustom",
"first_value",
"flatten",
"flattenTuple",
"floor",
"format",
"formatDateTime",
"formatReadableQuantity",
"formatReadableSize",
"formatReadableTimeDelta",
"formatRow",
"formatRowNoNewline",
"fragment",
"fromModifiedJulianDay",
"fromModifiedJulianDayOrNull",
"fromUnixTimestamp",
"fromUnixTimestamp64Micro",
"fromUnixTimestamp64Milli",
"fromUnixTimestamp64Nano",
"fullHostName",
"fuzzBits",
"gccMurmurHash",
"gcd",
"generateUUIDv4",
"geoDistance",
"geoToH3",
"geoToS2",
"geohashDecode",
"geohashEncode",
"geohashesInBox",
"getMacro",
"getOSKernelVersion",
"getServerPort",
"getSetting",
"getSizeOfEnumType",
"getTypeSerializationStreams",
"globalIn",
"globalInIgnoreSet",
"globalNotIn",
"globalNotInIgnoreSet",
"globalNotNullIn",
"globalNotNullInIgnoreSet",
"globalNullIn",
"globalNullInIgnoreSet",
"globalVariable",
"greatCircleAngle",
"greatCircleDistance",
"greater",
"greaterOrEquals",
"greatest",
"groupArray",
"groupArrayInsertAt",
"groupArrayMovingAvg",
"groupArrayMovingSum",
"groupArraySample",
"groupBitAnd",
"groupBitOr",
"groupBitXor",
"groupBitmap",
"groupBitmapAnd",
"groupBitmapOr",
"groupBitmapXor",
"groupUniqArray",
"h3CellAreaM2",
"h3CellAreaRads2",
"h3Distance",
"h3EdgeAngle",
"h3EdgeLengthKm",
"h3EdgeLengthM",
"h3ExactEdgeLengthKm",
"h3ExactEdgeLengthM",
"h3ExactEdgeLengthRads",
"h3GetBaseCell",
"h3GetDestinationIndexFromUnidirectionalEdge",
"h3GetFaces",
"h3GetIndexesFromUnidirectionalEdge",
"h3GetOriginIndexFromUnidirectionalEdge",
"h3GetPentagonIndexes",
"h3GetRes0Indexes",
"h3GetResolution",
"h3GetUnidirectionalEdge",
"h3GetUnidirectionalEdgeBoundary",
"h3GetUnidirectionalEdgesFromHexagon",
"h3HexAreaKm2",
"h3HexAreaM2",
"h3HexRing",
"h3IndexesAreNeighbors",
"h3IsPentagon",
"h3IsResClassIII",
"h3IsValid",
"h3Line",
"h3NumHexagons",
"h3PointDistKm",
"h3PointDistM",
"h3PointDistRads",
"h3ToCenterChild",
"h3ToChildren",
"h3ToGeo",
"h3ToGeoBoundary",
"h3ToParent",
"h3ToString",
"h3UnidirectionalEdgeIsValid",
"h3kRing",
"halfMD5",
"has",
"hasAll",
"hasAny",
"hasColumnInTable",
"hasSubstr",
"hasThreadFuzzer",
"hasToken",
"hasTokenCaseInsensitive",
"hashid",
"hex",
"histogram",
"hiveHash",
"hop",
"hopEnd",
"hopStart",
"hostName",
"hostname",
"hypot",
"identity",
"if",
"ifNotFinite",
"ifNull",
"ignore",
"ilike",
"in",
"inIgnoreSet",
"indexHint",
"indexOf",
"initialQueryID",
"initial_query_id",
"initializeAggregation",
"intDiv",
"intDivOrZero",
"intExp10",
"intExp2",
"intHash32",
"intHash64",
"intervalLengthSum",
"isConstant",
"isDecimalOverflow",
"isFinite",
"isIPAddressInRange",
"isIPv4String",
"isIPv6String",
"isInfinite",
"isNaN",
"isNotNull",
"isNull",
"isNullable",
"isValidJSON",
"isValidUTF8",
"isZeroOrNull",
"javaHash",
"javaHashUTF16LE",
"joinGet",
"joinGetOrNull",
"jumpConsistentHash",
"kostikConsistentHash",
"kurtPop",
"kurtSamp",
"lagInFrame",
"last_value",
"lcase",
"lcm",
"leadInFrame",
"least",
"left",
"leftPad",
"leftPadUTF8",
"leftUTF8",
"lemmatize",
"length",
"lengthUTF8",
"less",
"lessOrEquals",
"lgamma",
"like",
"ln",
"locate",
"log",
"log10",
"log1p",
"log2",
"logTrace",
"lowCardinalityIndices",
"lowCardinalityKeys",
"lower",
"lowerUTF8",
"lpad",
"makeDate",
"makeDate32",
"makeDateTime",
"makeDateTime64",
"mannWhitneyUTest",
"map",
"mapAdd",
"mapApply",
"mapContains",
"mapContainsKeyLike",
"mapExtractKeyLike",
"mapFilter",
"mapKeys",
"mapPopulateSeries",
"mapSubtract",
"mapUpdate",
"mapValues",
"match",
"materialize",
"max",
"max2",
"maxIntersections",
"maxIntersectionsPosition",
"maxMappedArrays",
"meanZTest",
"median",
"medianBFloat16",
"medianBFloat16Weighted",
"medianDeterministic",
"medianExact",
"medianExactHigh",
"medianExactLow",
"medianExactWeighted",
"medianTDigest",
"medianTDigestWeighted",
"medianTiming",
"medianTimingWeighted",
"meiliMatch",
"metroHash64",
"mid",
"min",
"min2",
"minMappedArrays",
"minSampleSizeContinous",
"minSampleSizeConversion",
"minus",
"mod",
"modelEvaluate",
"modulo",
"moduloLegacy",
"moduloOrZero",
"monthName",
"multiFuzzyMatchAllIndices",
"multiFuzzyMatchAny",
"multiFuzzyMatchAnyIndex",
"multiIf",
"multiMatchAllIndices",
"multiMatchAny",
"multiMatchAnyIndex",
"multiSearchAllPositions",
"multiSearchAllPositionsCaseInsensitive",
"multiSearchAllPositionsCaseInsensitiveUTF8",
"multiSearchAllPositionsUTF8",
"multiSearchAny",
"multiSearchAnyCaseInsensitive",
"multiSearchAnyCaseInsensitiveUTF8",
"multiSearchAnyUTF8",
"multiSearchFirstIndex",
"multiSearchFirstIndexCaseInsensitive",
"multiSearchFirstIndexCaseInsensitiveUTF8",
"multiSearchFirstIndexUTF8",
"multiSearchFirstPosition",
"multiSearchFirstPositionCaseInsensitive",
"multiSearchFirstPositionCaseInsensitiveUTF8",
"multiSearchFirstPositionUTF8",
"multiply",
"murmurHash2_32",
"murmurHash2_64",
"murmurHash3_128",
"murmurHash3_32",
"murmurHash3_64",
"negate",
"neighbor",
"netloc",
"ngramDistance",
"ngramDistanceCaseInsensitive",
"ngramDistanceCaseInsensitiveUTF8",
"ngramDistanceUTF8",
"ngramMinHash",
"ngramMinHashArg",
"ngramMinHashArgCaseInsensitive",
"ngramMinHashArgCaseInsensitiveUTF8",
"ngramMinHashArgUTF8",
"ngramMinHashCaseInsensitive",
"ngramMinHashCaseInsensitiveUTF8",
"ngramMinHashUTF8",
"ngramSearch",
"ngramSearchCaseInsensitive",
"ngramSearchCaseInsensitiveUTF8",
"ngramSearchUTF8",
"ngramSimHash",
"ngramSimHashCaseInsensitive",
"ngramSimHashCaseInsensitiveUTF8",
"ngramSimHashUTF8",
"ngrams",
"nonNegativeDerivative",
"normL1",
"normL2",
"normL2Squared",
"normLinf",
"normLp",
"normalizeL1",
"normalizeL2",
"normalizeLinf",
"normalizeLp",
"normalizeQuery",
"normalizeQueryKeepNames",
"normalizeUTF8NFC",
"normalizeUTF8NFD",
"normalizeUTF8NFKC",
"normalizeUTF8NFKD",
"normalizedQueryHash",
"normalizedQueryHashKeepNames",
"not",
"notEmpty",
"notEquals",
"notILike",
"notIn",
"notInIgnoreSet",
"notLike",
"notNullIn",
"notNullInIgnoreSet",
"nothing",
"now",
"now64",
"nth_value",
"nullIf",
"nullIn",
"nullInIgnoreSet",
"or",
"parseDateTime32BestEffort",
"parseDateTime32BestEffortOrNull",
"parseDateTime32BestEffortOrZero",
"parseDateTime64BestEffort",
"parseDateTime64BestEffortOrNull",
"parseDateTime64BestEffortOrZero",
"parseDateTimeBestEffort",
"parseDateTimeBestEffortOrNull",
"parseDateTimeBestEffortOrZero",
"parseDateTimeBestEffortUS",
"parseDateTimeBestEffortUSOrNull",
"parseDateTimeBestEffortUSOrZero",
"parseTimeDelta",
"partitionId",
"path",
"pathFull",
"pi",
"plus",
"pointInEllipses",
"pointInPolygon",
"polygonAreaCartesian",
"polygonAreaSpherical",
"polygonConvexHullCartesian",
"polygonPerimeterCartesian",
"polygonPerimeterSpherical",
"polygonsDistanceCartesian",
"polygonsDistanceSpherical",
"polygonsEqualsCartesian",
"polygonsIntersectionCartesian",
"polygonsIntersectionSpherical",
"polygonsSymDifferenceCartesian",
"polygonsSymDifferenceSpherical",
"polygonsUnionCartesian",
"polygonsUnionSpherical",
"polygonsWithinCartesian",
"polygonsWithinSpherical",
"port",
"position",
"positionCaseInsensitive",
"positionCaseInsensitiveUTF8",
"positionUTF8",
"pow",
"power",
"proportionsZTest",
"protocol",
"quantile",
"quantileBFloat16",
"quantileBFloat16Weighted",
"quantileDeterministic",
"quantileExact",
"quantileExactExclusive",
"quantileExactHigh",
"quantileExactInclusive",
"quantileExactLow",
"quantileExactWeighted",
"quantileTDigest",
"quantileTDigestWeighted",
"quantileTiming",
"quantileTimingWeighted",
"quantiles",
"quantilesBFloat16",
"quantilesBFloat16Weighted",
"quantilesDeterministic",
"quantilesExact",
"quantilesExactExclusive",
"quantilesExactHigh",
"quantilesExactInclusive",
"quantilesExactLow",
"quantilesExactWeighted",
"quantilesTDigest",
"quantilesTDigestWeighted",
"quantilesTiming",
"quantilesTimingWeighted",
"queryID",
"queryString",
"queryStringAndFragment",
"query_id",
"radians",
"rand",
"rand32",
"rand64",
"randConstant",
"randomFixedString",
"randomPrintableASCII",
"randomString",
"randomStringUTF8",
"range",
"rank",
"rankCorr",
"readWKTMultiPolygon",
"readWKTPoint",
"readWKTPolygon",
"readWKTRing",
"regexpQuoteMeta",
"regionHierarchy",
"regionIn",
"regionToArea",
"regionToCity",
"regionToContinent",
"regionToCountry",
"regionToDistrict",
"regionToName",
"regionToPopulation",
"regionToTopContinent",
"reinterpret",
"reinterpretAsDate",
"reinterpretAsDateTime",
"reinterpretAsFixedString",
"reinterpretAsFloat32",
"reinterpretAsFloat64",
"reinterpretAsInt128",
"reinterpretAsInt16",
"reinterpretAsInt256",
"reinterpretAsInt32",
"reinterpretAsInt64",
"reinterpretAsInt8",
"reinterpretAsString",
"reinterpretAsUInt128",
"reinterpretAsUInt16",
"reinterpretAsUInt256",
"reinterpretAsUInt32",
"reinterpretAsUInt64",
"reinterpretAsUInt8",
"reinterpretAsUUID",
"repeat",
"replace",
"replaceAll",
"replaceOne",
"replaceRegexpAll",
"replaceRegexpOne",
"replicate",
"retention",
"reverse",
"reverseUTF8",
"revision",
"right",
"rightPad",
"rightPadUTF8",
"rightUTF8",
"round",
"roundAge",
"roundBankers",
"roundDown",
"roundDuration",
"roundToExp2",
"rowNumberInAllBlocks",
"rowNumberInBlock",
"row_number",
"rpad",
"runningAccumulate",
"runningConcurrency",
"runningDifference",
"runningDifferenceStartingWithFirstValue",
"s2CapContains",
"s2CapUnion",
"s2CellsIntersect",
"s2GetNeighbors",
"s2RectAdd",
"s2RectContains",
"s2RectIntersection",
"s2RectUnion",
"s2ToGeo",
"scalarProduct",
"sequenceCount",
"sequenceMatch",
"sequenceNextNode",
"serverUUID",
"shardCount",
"shardNum",
"showCertificate",
"sigmoid",
"sign",
"simpleJSONExtractBool",
"simpleJSONExtractFloat",
"simpleJSONExtractInt",
"simpleJSONExtractRaw",
"simpleJSONExtractString",
"simpleJSONExtractUInt",
"simpleJSONHas",
"simpleLinearRegression",
"sin",
"singleValueOrNull",
"sinh",
"sipHash128",
"sipHash64",
"skewPop",
"skewSamp",
"sleep",
"sleepEachRow",
"snowflakeToDateTime",
"snowflakeToDateTime64",
"sparkbar",
"splitByChar",
"splitByNonAlpha",
"splitByRegexp",
"splitByString",
"splitByWhitespace",
"sqrt",
"startsWith",
"stddevPop",
"stddevPopStable",
"stddevSamp",
"stddevSampStable",
"stem",
"stochasticLinearRegression",
"stochasticLogisticRegression",
"stringToH3",
"studentTTest",
"subBitmap",
"substr",
"substring",
"substringUTF8",
"subtractDays",
"subtractHours",
"subtractMicroseconds",
"subtractMilliseconds",
"subtractMinutes",
"subtractMonths",
"subtractNanoseconds",
"subtractQuarters",
"subtractSeconds",
"subtractWeeks",
"subtractYears",
"sum",
"sumCount",
"sumKahan",
"sumMapFiltered",
"sumMapFilteredWithOverflow",
"sumMapWithOverflow",
"sumMappedArrays",
"sumWithOverflow",
"svg",
"synonyms",
"tan",
"tanh",
"tcpPort",
"tgamma",
"theilsU",
"throwIf",
"tid",
"timeSlot",
"timeSlots",
"timeZone",
"timeZoneOf",
"timeZoneOffset",
"timezone",
"timezoneOf",
"timezoneOffset",
"toBool",
"toColumnTypeName",
"toDate",
"toDate32",
"toDate32OrDefault",
"toDate32OrNull",
"toDate32OrZero",
"toDateOrDefault",
"toDateOrNull",
"toDateOrZero",
"toDateTime",
"toDateTime32",
"toDateTime64",
"toDateTime64OrDefault",
"toDateTime64OrNull",
"toDateTime64OrZero",
"toDateTimeOrDefault",
"toDateTimeOrNull",
"toDateTimeOrZero",
"toDayOfMonth",
"toDayOfWeek",
"toDayOfYear",
"toDecimal128",
"toDecimal128OrDefault",
"toDecimal128OrNull",
"toDecimal128OrZero",
"toDecimal256",
"toDecimal256OrDefault",
"toDecimal256OrNull",
"toDecimal256OrZero",
"toDecimal32",
"toDecimal32OrDefault",
"toDecimal32OrNull",
"toDecimal32OrZero",
"toDecimal64",
"toDecimal64OrDefault",
"toDecimal64OrNull",
"toDecimal64OrZero",
"toFixedString",
"toFloat32",
"toFloat32OrDefault",
"toFloat32OrNull",
"toFloat32OrZero",
"toFloat64",
"toFloat64OrDefault",
"toFloat64OrNull",
"toFloat64OrZero",
"toHour",
"toIPv4",
"toIPv4OrDefault",
"toIPv4OrNull",
"toIPv6",
"toIPv6OrDefault",
"toIPv6OrNull",
"toISOWeek",
"toISOYear",
"toInt128",
"toInt128OrDefault",
"toInt128OrNull",
"toInt128OrZero",
"toInt16",
"toInt16OrDefault",
"toInt16OrNull",
"toInt16OrZero",
"toInt256",
"toInt256OrDefault",
"toInt256OrNull",
"toInt256OrZero",
"toInt32",
"toInt32OrDefault",
"toInt32OrNull",
"toInt32OrZero",
"toInt64",
"toInt64OrDefault",
"toInt64OrNull",
"toInt64OrZero",
"toInt8",
"toInt8OrDefault",
"toInt8OrNull",
"toInt8OrZero",
"toIntervalDay",
"toIntervalHour",
"toIntervalMicrosecond",
"toIntervalMillisecond",
"toIntervalMinute",
"toIntervalMonth",
"toIntervalNanosecond",
"toIntervalQuarter",
"toIntervalSecond",
"toIntervalWeek",
"toIntervalYear",
"toJSONString",
"toLastDayOfMonth",
"toLowCardinality",
"toMinute",
"toModifiedJulianDay",
"toModifiedJulianDayOrNull",
"toMonday",
"toMonth",
"toNullable",
"toQuarter",
"toRelativeDayNum",
"toRelativeHourNum",
"toRelativeMinuteNum",
"toRelativeMonthNum",
"toRelativeQuarterNum",
"toRelativeSecondNum",
"toRelativeWeekNum",
"toRelativeYearNum",
"toSecond",
"toStartOfDay",
"toStartOfFifteenMinutes",
"toStartOfFiveMinute",
"toStartOfFiveMinutes",
"toStartOfHour",
"toStartOfISOYear",
"toStartOfInterval",
"toStartOfMicrosecond",
"toStartOfMillisecond",
"toStartOfMinute",
"toStartOfMonth",
"toStartOfNanosecond",
"toStartOfQuarter",
"toStartOfSecond",
"toStartOfTenMinutes",
"toStartOfWeek",
"toStartOfYear",
"toString",
"toStringCutToZero",
"toTime",
"toTimeZone",
"toTimezone",
"toTypeName",
"toUInt128",
"toUInt128OrNull",
"toUInt128OrZero",
"toUInt16",
"toUInt16OrDefault",
"toUInt16OrNull",
"toUInt16OrZero",
"toUInt256",
"toUInt256OrDefault",
"toUInt256OrNull",
"toUInt256OrZero",
"toUInt32",
"toUInt32OrDefault",
"toUInt32OrNull",
"toUInt32OrZero",
"toUInt64",
"toUInt64OrDefault",
"toUInt64OrNull",
"toUInt64OrZero",
"toUInt8",
"toUInt8OrDefault",
"toUInt8OrNull",
"toUInt8OrZero",
"toUUID",
"toUUIDOrDefault",
"toUUIDOrNull",
"toUUIDOrZero",
"toUnixTimestamp",
"toUnixTimestamp64Micro",
"toUnixTimestamp64Milli",
"toUnixTimestamp64Nano",
"toValidUTF8",
"toWeek",
"toYYYYMM",
"toYYYYMMDD",
"toYYYYMMDDhhmmss",
"toYear",
"toYearWeek",
"today",
"tokens",
"topK",
"topKWeighted",
"topLevelDomain",
"transactionID",
"transactionLatestSnapshot",
"transactionOldestSnapshot",
"transform",
"translate",
"translateUTF8",
"trimBoth",
"trimLeft",
"trimRight",
"trunc",
"truncate",
"tryBase64Decode",
"tumble",
"tumbleEnd",
"tumbleStart",
"tuple",
"tupleDivide",
"tupleDivideByNumber",
"tupleElement",
"tupleHammingDistance",
"tupleMinus",
"tupleMultiply",
"tupleMultiplyByNumber",
"tupleNegate",
"tuplePlus",
"tupleToNameValuePairs",
"ucase",
"unbin",
"unhex",
"uniq",
"uniqCombined",
"uniqCombined64",
"uniqExact",
"uniqHLL12",
"uniqTheta",
"uniqUpTo",
"upper",
"upperUTF8",
"uptime",
"user",
"validateNestedArraySizes",
"varPop",
"varPopStable",
"varSamp",
"varSampStable",
"vectorDifference",
"vectorSum",
"version",
"visibleWidth",
"visitParamExtractBool",
"visitParamExtractFloat",
"visitParamExtractInt",
"visitParamExtractRaw",
"visitParamExtractString",
"visitParamExtractUInt",
"visitParamHas",
"week",
"welchTTest",
"windowFunnel",
"windowID",
"wkt",
"wordShingleMinHash",
"wordShingleMinHashArg",
"wordShingleMinHashArgCaseInsensitive",
"wordShingleMinHashArgCaseInsensitiveUTF8",
"wordShingleMinHashArgUTF8",
"wordShingleMinHashCaseInsensitive",
"wordShingleMinHashCaseInsensitiveUTF8",
"wordShingleMinHashUTF8",
"wordShingleSimHash",
"wordShingleSimHashCaseInsensitive",
"wordShingleSimHashCaseInsensitiveUTF8",
"wordShingleSimHashUTF8",
"wyHash64",
"xor",
"xxHash32",
"xxHash64",
"yandexConsistentHash",
"yearweek",
"yesterday",
"zookeeperSessionUptime",
}
}
func colNames() []string {
return []string{
"AdvEngineID",
"Age",
"BrowserCountry",
"BrowserLanguage",
"CLID",
"ClientEventTime",
"ClientIP",
"ClientIP",
"ClientIP6",
"ClientTimeZone",
"CodeVersion",
"ConnectTiming",
"CookieEnable",
"CounterClass",
"CounterID",
"DNSTiming",
"DOMCompleteTiming",
"DOMContentLoadedTiming",
"DOMInteractiveTiming",
"DontCountHits",
"EventDate",
"EventTime",
"FUniqID",
"FetchTiming",
"FirstPaintTiming",
"FlashMajor",
"FlashMinor",
"FlashMinor2",
"FromTag",
"GeneralInterests",
"GoalsReached",
"GoodEvent",
"HID",
"HTTPError",
"HasGCLID",
"HistoryLength",
"HitColor",
"IPNetworkID",
"Income",
"Interests",
"IsArtifical",
"IsDownload",
"IsEvent",
"IsLink",
"IsMobile",
"IsNotBounce",
"IsOldCounter",
"IsParameter",
"IsRobot",
"IslandID",
"JavaEnable",
"JavascriptEnable",
"LoadEventEndTiming",
"LoadEventStartTiming",
"MobilePhone",
"MobilePhoneModel",
"NSToDOMContentLoadedTiming",
"NetMajor",
"NetMinor",
"OS",
"OpenerName",
"OpenstatAdID",
"OpenstatCampaignID",
"OpenstatServiceName",
"OpenstatSourceID",
"PageCharset",
"ParamCurrency",
"ParamCurrencyID",
"ParamOrderID",
"ParamPrice",
"Params",
"ParsedParams.Key1",
"ParsedParams.Key2",
"ParsedParams.Key3",
"ParsedParams.Key4",
"ParsedParams.Key5",
"ParsedParams.ValueDouble",
"RedirectCount",
"RedirectTiming",
"Referer",
"RefererCategories",
"RefererDomain",
"RefererHash",
"RefererRegions",
"Refresh",
"RegionID",
"RemoteIP",
"RemoteIP6",
"RequestNum",
"RequestTry",
"ResolutionDepth",
"ResolutionHeight",
"ResolutionWidth",
"ResponseEndTiming",
"ResponseStartTiming",
"Robotness",
"SearchEngineID",
"SearchPhrase",
"SendTiming",
"Sex",
"ShareService",
"ShareTitle",
"ShareURL",
"SilverlightVersion1",
"SilverlightVersion2",
"SilverlightVersion3",
"SilverlightVersion4",
"SocialAction",
"SocialNetwork",
"SocialSourceNetworkID",
"SocialSourcePage",
"Title",
"TraficSourceID",
"URL",
"URLCategories",
"URLDomain",
"URLHash",
"URLRegions",
"UTCEventTime",
"UTMCampaign",
"UTMContent",
"UTMMedium",
"UTMSource",
"UTMTerm",
"UserAgent",
"UserAgentMajor",
"UserAgentMinor",
"UserID",
"WatchID",
"WindowClientHeight",
"WindowClientWidth",
"WindowName",
"WithHash",
"YCLID",
}
}
================================================
FILE: drivers/clickhouse/reader.go
================================================
package clickhouse
import (
"database/sql"
"strings"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
)
type MetadataReader struct {
metadata.LoggingReader
}
// NewMetadataReader creates the metadata reader for clickhouse databases.
func NewMetadataReader(db drivers.DB, opts ...metadata.ReaderOption) metadata.Reader {
return &MetadataReader{
LoggingReader: metadata.NewLoggingReader(db, opts...),
}
}
func (r MetadataReader) Tables(f metadata.Filter) (*metadata.TableSet, error) {
qstr := `SELECT
database AS Schema,
name AS Name,
COALESCE(
IF(database LIKE 'system', 'SYSTEM TABLE', null),
IF(is_temporary,'LOCAL TEMPORARY', null),
IF(engine LIKE 'View', 'VIEW', null),
'TABLE'
) AS Type,
COALESCE(total_bytes, 0) AS Size,
comment as Comment
FROM
system.tables`
var conds []string
var vals []interface{}
if !f.WithSystem {
conds = append(conds, "database NOT LIKE 'system'")
}
if f.Schema != "" {
vals = append(vals, f.Schema)
conds = append(conds, "database LIKE ?")
}
if f.Name != "" {
vals = append(vals, f.Name)
conds = append(conds, "name LIKE ?")
}
if len(f.Types) != 0 {
var pholders []string
for _, t := range f.Types {
vals = append(vals, t)
pholders = append(pholders, "?")
}
if len(pholders) != 0 {
conds = append(conds, "Type IN ("+strings.Join(pholders, ", ")+")")
}
}
rows, closeRows, err := r.query(qstr, conds, "Schema, Name", vals...)
if err != nil {
return nil, err
}
defer closeRows()
var results []metadata.Table
for rows.Next() {
var rec metadata.Table
if err := rows.Scan(&rec.Schema, &rec.Name, &rec.Type, &rec.Size, &rec.Comment); err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewTableSet(results), nil
}
func (r MetadataReader) Columns(f metadata.Filter) (*metadata.ColumnSet, error) {
qstr := `SELECT
position,
database as schema,
name,
type,
COALESCE(default_expression, '')
FROM
system.columns`
vals := []interface{}{f.Parent}
conds := []string{"table LIKE ?"}
if f.Schema != "" {
vals = append(vals, f.Schema)
conds = append(conds, "database LIKE ?")
}
if f.Name != "" {
vals = append(vals, f.Name)
conds = append(conds, "name LIKE ?")
}
if len(f.Types) != 0 {
var pholders []string
for _, t := range f.Types {
vals = append(vals, t)
pholders = append(pholders, "?")
}
if len(pholders) != 0 {
conds = append(conds, "Type IN ("+strings.Join(pholders, ", ")+")")
}
}
rows, closeRows, err := r.query(qstr, conds, "name", vals...)
if err != nil {
return nil, err
}
defer closeRows()
var results []metadata.Column
for rows.Next() {
rec := metadata.Column{
Catalog: f.Catalog,
Table: f.Parent,
}
if err := rows.Scan(
&rec.OrdinalPosition,
&rec.Schema,
&rec.Name,
&rec.DataType,
&rec.Default,
); err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewColumnSet(results), nil
}
func (r MetadataReader) Schemas(f metadata.Filter) (*metadata.SchemaSet, error) {
qstr := `SELECT
name
FROM
system.databases`
var conds []string
var vals []interface{}
if f.Name != "" {
vals = append(vals, f.Name)
conds = append(conds, "name LIKE ?")
}
rows, closeRows, err := r.query(qstr, conds, "name", vals...)
if err != nil {
return nil, err
}
defer closeRows()
var results []metadata.Schema
for rows.Next() {
var rec metadata.Schema
if err := rows.Scan(&rec.Schema); err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewSchemaSet(results), nil
}
func (r MetadataReader) Functions(f metadata.Filter) (*metadata.FunctionSet, error) {
qstr := `SELECT
name AS specific_name,
name AS routine_name,
(IF(is_aggregate = 1,'AGGREGATE','FUNCTION')) AS type
FROM
system.functions`
var conds []string
var vals []interface{}
if f.Name != "" {
conds = append(conds, "name LIKE ?")
vals = append(vals, f.Name)
}
if len(f.Types) != 0 {
var pholders []string
for _, t := range f.Types {
vals = append(vals, t)
pholders = append(pholders, "?")
}
if len(pholders) != 0 {
conds = append(conds, "type IN ("+strings.Join(pholders, ", ")+")")
}
}
rows, closeRows, err := r.query(qstr, conds, "name, type", vals...)
if err != nil {
return nil, err
}
defer closeRows()
var results []metadata.Function
for rows.Next() {
var rec metadata.Function
if err := rows.Scan(
&rec.SpecificName,
&rec.Name,
&rec.Type,
); err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewFunctionSet(results), nil
}
func (r MetadataReader) query(qstr string, conds []string, order string, vals ...interface{}) (*sql.Rows, func(), error) {
if len(conds) != 0 {
qstr += "\nWHERE " + strings.Join(conds, " AND ")
}
if order != "" {
qstr += "\nORDER BY " + order
}
return r.Query(qstr, vals...)
}
================================================
FILE: drivers/clickhouse/testdata/clickhouse.sql
================================================
-- https://clickhouse.tech/docs/en/getting-started/tutorial/#create-tables
CREATE DATABASE tutorial;
CREATE TABLE tutorial.hits_v1 (
`WatchID` UInt64,
`JavaEnable` UInt8,
`Title` String,
`GoodEvent` Int16,
`EventTime` DateTime,
`EventDate` Date,
`CounterID` UInt32,
`ClientIP` UInt32,
`ClientIP6` FixedString(16),
`RegionID` UInt32,
`UserID` UInt64,
`CounterClass` Int8,
`OS` UInt8,
`UserAgent` UInt8,
`URL` String,
`Referer` String,
`URLDomain` String,
`RefererDomain` String,
`Refresh` UInt8,
`IsRobot` UInt8,
`RefererCategories` Array(UInt16),
`URLCategories` Array(UInt16),
`URLRegions` Array(UInt32),
`RefererRegions` Array(UInt32),
`ResolutionWidth` UInt16,
`ResolutionHeight` UInt16,
`ResolutionDepth` UInt8,
`FlashMajor` UInt8,
`FlashMinor` UInt8,
`FlashMinor2` String,
`NetMajor` UInt8,
`NetMinor` UInt8,
`UserAgentMajor` UInt16,
`UserAgentMinor` FixedString(2),
`CookieEnable` UInt8,
`JavascriptEnable` UInt8,
`IsMobile` UInt8,
`MobilePhone` UInt8,
`MobilePhoneModel` String,
`Params` String,
`IPNetworkID` UInt32,
`TraficSourceID` Int8,
`SearchEngineID` UInt16,
`SearchPhrase` String,
`AdvEngineID` UInt8,
`IsArtifical` UInt8,
`WindowClientWidth` UInt16,
`WindowClientHeight` UInt16,
`ClientTimeZone` Int16,
`ClientEventTime` DateTime,
`SilverlightVersion1` UInt8,
`SilverlightVersion2` UInt8,
`SilverlightVersion3` UInt32,
`SilverlightVersion4` UInt16,
`PageCharset` String,
`CodeVersion` UInt32,
`IsLink` UInt8,
`IsDownload` UInt8,
`IsNotBounce` UInt8,
`FUniqID` UInt64,
`HID` UInt32,
`IsOldCounter` UInt8,
`IsEvent` UInt8,
`IsParameter` UInt8,
`DontCountHits` UInt8,
`WithHash` UInt8,
`HitColor` FixedString(1),
`UTCEventTime` DateTime,
`Age` UInt8,
`Sex` UInt8,
`Income` UInt8,
`Interests` UInt16,
`Robotness` UInt8,
`GeneralInterests` Array(UInt16),
`RemoteIP` UInt32,
`RemoteIP6` FixedString(16),
`WindowName` Int32,
`OpenerName` Int32,
`HistoryLength` Int16,
`BrowserLanguage` FixedString(2),
`BrowserCountry` FixedString(2),
`SocialNetwork` String,
`SocialAction` String,
`HTTPError` UInt16,
`SendTiming` Int32,
`DNSTiming` Int32,
`ConnectTiming` Int32,
`ResponseStartTiming` Int32,
`ResponseEndTiming` Int32,
`FetchTiming` Int32,
`RedirectTiming` Int32,
`DOMInteractiveTiming` Int32,
`DOMContentLoadedTiming` Int32,
`DOMCompleteTiming` Int32,
`LoadEventStartTiming` Int32,
`LoadEventEndTiming` Int32,
`NSToDOMContentLoadedTiming` Int32,
`FirstPaintTiming` Int32,
`RedirectCount` Int8,
`SocialSourceNetworkID` UInt8,
`SocialSourcePage` String,
`ParamPrice` Int64,
`ParamOrderID` String,
`ParamCurrency` FixedString(3),
`ParamCurrencyID` UInt16,
`GoalsReached` Array(UInt32),
`OpenstatServiceName` String,
`OpenstatCampaignID` String,
`OpenstatAdID` String,
`OpenstatSourceID` String,
`UTMSource` String,
`UTMMedium` String,
`UTMCampaign` String,
`UTMContent` String,
`UTMTerm` String,
`FromTag` String,
`HasGCLID` UInt8,
`RefererHash` UInt64,
`URLHash` UInt64,
`CLID` UInt32,
`YCLID` UInt64,
`ShareService` String,
`ShareURL` String,
`ShareTitle` String,
`ParsedParams` Nested(
Key1 String,
Key2 String,
Key3 String,
Key4 String,
Key5 String,
ValueDouble Float64),
`IslandID` FixedString(16),
`RequestNum` UInt32,
`RequestTry` UInt8
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate, intHash32(UserID))
SAMPLE BY intHash32(UserID);
CREATE TABLE tutorial.visits_v1 (
`CounterID` UInt32,
`StartDate` Date,
`Sign` Int8,
`IsNew` UInt8,
`VisitID` UInt64,
`UserID` UInt64,
`StartTime` DateTime,
`Duration` UInt32,
`UTCStartTime` DateTime,
`PageViews` Int32,
`Hits` Int32,
`IsBounce` UInt8,
`Referer` String,
`StartURL` String,
`RefererDomain` String,
`StartURLDomain` String,
`EndURL` String,
`LinkURL` String,
`IsDownload` UInt8,
`TraficSourceID` Int8,
`SearchEngineID` UInt16,
`SearchPhrase` String,
`AdvEngineID` UInt8,
`PlaceID` Int32,
`RefererCategories` Array(UInt16),
`URLCategories` Array(UInt16),
`URLRegions` Array(UInt32),
`RefererRegions` Array(UInt32),
`IsYandex` UInt8,
`GoalReachesDepth` Int32,
`GoalReachesURL` Int32,
`GoalReachesAny` Int32,
`SocialSourceNetworkID` UInt8,
`SocialSourcePage` String,
`MobilePhoneModel` String,
`ClientEventTime` DateTime,
`RegionID` UInt32,
`ClientIP` UInt32,
`ClientIP6` FixedString(16),
`RemoteIP` UInt32,
`RemoteIP6` FixedString(16),
`IPNetworkID` UInt32,
`SilverlightVersion3` UInt32,
`CodeVersion` UInt32,
`ResolutionWidth` UInt16,
`ResolutionHeight` UInt16,
`UserAgentMajor` UInt16,
`UserAgentMinor` UInt16,
`WindowClientWidth` UInt16,
`WindowClientHeight` UInt16,
`SilverlightVersion2` UInt8,
`SilverlightVersion4` UInt16,
`FlashVersion3` UInt16,
`FlashVersion4` UInt16,
`ClientTimeZone` Int16,
`OS` UInt8,
`UserAgent` UInt8,
`ResolutionDepth` UInt8,
`FlashMajor` UInt8,
`FlashMinor` UInt8,
`NetMajor` UInt8,
`NetMinor` UInt8,
`MobilePhone` UInt8,
`SilverlightVersion1` UInt8,
`Age` UInt8,
`Sex` UInt8,
`Income` UInt8,
`JavaEnable` UInt8,
`CookieEnable` UInt8,
`JavascriptEnable` UInt8,
`IsMobile` UInt8,
`BrowserLanguage` UInt16,
`BrowserCountry` UInt16,
`Interests` UInt16,
`Robotness` UInt8,
`GeneralInterests` Array(UInt16),
`Params` Array(String),
`Goals` Nested(
ID UInt32,
Serial UInt32,
EventTime DateTime,
Price Int64,
OrderID String,
CurrencyID UInt32),
`WatchIDs` Array(UInt64),
`ParamSumPrice` Int64,
`ParamCurrency` FixedString(3),
`ParamCurrencyID` UInt16,
`ClickLogID` UInt64,
`ClickEventID` Int32,
`ClickGoodEvent` Int32,
`ClickEventTime` DateTime,
`ClickPriorityID` Int32,
`ClickPhraseID` Int32,
`ClickPageID` Int32,
`ClickPlaceID` Int32,
`ClickTypeID` Int32,
`ClickResourceID` Int32,
`ClickCost` UInt32,
`ClickClientIP` UInt32,
`ClickDomainID` UInt32,
`ClickURL` String,
`ClickAttempt` UInt8,
`ClickOrderID` UInt32,
`ClickBannerID` UInt32,
`ClickMarketCategoryID` UInt32,
`ClickMarketPP` UInt32,
`ClickMarketCategoryName` String,
`ClickMarketPPName` String,
`ClickAWAPSCampaignName` String,
`ClickPageName` String,
`ClickTargetType` UInt16,
`ClickTargetPhraseID` UInt64,
`ClickContextType` UInt8,
`ClickSelectType` Int8,
`ClickOptions` String,
`ClickGroupBannerID` Int32,
`OpenstatServiceName` String,
`OpenstatCampaignID` String,
`OpenstatAdID` String,
`OpenstatSourceID` String,
`UTMSource` String,
`UTMMedium` String,
`UTMCampaign` String,
`UTMContent` String,
`UTMTerm` String,
`FromTag` String,
`HasGCLID` UInt8,
`FirstVisit` DateTime,
`PredLastVisit` Date,
`LastVisit` Date,
`TotalVisits` UInt32,
`TraficSource` Nested(
ID Int8,
SearchEngineID UInt16,
AdvEngineID UInt8,
PlaceID UInt16,
SocialSourceNetworkID UInt8,
Domain String,
SearchPhrase String,
SocialSourcePage String),
`Attendance` FixedString(16),
`CLID` UInt32,
`YCLID` UInt64,
`NormalizedRefererHash` UInt64,
`SearchPhraseHash` UInt64,
`RefererDomainHash` UInt64,
`NormalizedStartURLHash` UInt64,
`StartURLDomainHash` UInt64,
`NormalizedEndURLHash` UInt64,
`TopLevelDomain` UInt64,
`URLScheme` UInt64,
`OpenstatServiceNameHash` UInt64,
`OpenstatCampaignIDHash` UInt64,
`OpenstatAdIDHash` UInt64,
`OpenstatSourceIDHash` UInt64,
`UTMSourceHash` UInt64,
`UTMMediumHash` UInt64,
`UTMCampaignHash` UInt64,
`UTMContentHash` UInt64,
`UTMTermHash` UInt64,
`FromHash` UInt64,
`WebVisorEnabled` UInt8,
`WebVisorActivity` UInt32,
`ParsedParams` Nested(
Key1 String,
Key2 String,
Key3 String,
Key4 String,
Key5 String,
ValueDouble Float64),
`Market` Nested(
Type UInt8,
GoalID UInt32,
OrderID String,
OrderPrice Int64,
PP UInt32,
DirectPlaceID UInt32,
DirectOrderID UInt32,
DirectBannerID UInt32,
GoodID String,
GoodName String,
GoodQuantity Int32,
GoodPrice Int64),
`IslandID` FixedString(16)
)
ENGINE = CollapsingMergeTree(Sign)
PARTITION BY toYYYYMM(StartDate)
ORDER BY (CounterID, StartDate, intHash32(UserID), VisitID)
SAMPLE BY intHash32(UserID);
CREATE DATABASE tutorial_unexpected;
CREATE TABLE tutorial_unexpected.hits_v1 (
`Unexpected` String
)
ENGINE = MergeTree()
ORDER BY (Unexpected);
CREATE DATABASE copy_test;
CREATE TABLE copy_test.dest (
StringCol String,
NumCol UInt32
)
ENGINE = MergeTree()
ORDER BY (StringCol);
================================================
FILE: drivers/completer/completer.go
================================================
// completer package provides a generic SQL command line completer
package completer
import (
"log"
"os"
"path/filepath"
"sort"
"strings"
"unicode"
"github.com/gohxs/readline"
"github.com/xo/usql/drivers/metadata"
"github.com/xo/usql/env"
"github.com/xo/usql/text"
)
const (
WORD_BREAKS = "\t\n$><=;|&{() "
)
type caseType bool
var (
IGNORE_CASE = caseType(true)
MATCH_CASE = caseType(false)
CommonSqlStartCommands = []string{
"ABORT",
"ALTER",
"ANALYZE",
"BEGIN",
"CALL",
"CHECKPOINT",
"CLOSE",
"CLUSTER",
"COMMENT",
"COMMIT",
"COPY",
"CREATE",
"DEALLOCATE",
"DECLARE",
"DELETE FROM",
"DESC",
"DESCRIBE",
"DISCARD",
"DO",
"DROP",
"END",
"EXEC",
"EXECUTE",
"EXPLAIN",
"FETCH",
"GRANT",
"IMPORT",
"INSERT",
"LIST",
"LISTEN",
"LOAD",
"LOCK",
"MOVE",
"NOTIFY",
"PRAGMA",
"PREPARE",
"REASSIGN",
"REFRESH MATERIALIZED VIEW",
"REINDEX",
"RELEASE",
"RESET",
"REVOKE",
"ROLLBACK",
"SAVEPOINT",
"SECURITY LABEL",
"SELECT",
"SET",
"SHOW",
"START",
"TABLE",
"TRUNCATE",
"UNLISTEN",
"UPDATE",
"VACUUM",
"VALUES",
"WITH",
}
CommonSqlCommands = []string{
"AND",
"CASE",
"CROSS JOIN",
"ELSE",
"END",
"FETCH",
"FROM",
"FULL OUTER JOIN",
"GROUP BY",
"HAVING",
"IN",
"INNER JOIN",
"IS NOT NULL",
"IS NULL",
"JOIN",
"LEFT JOIN",
"LIMIT",
"NOT",
"ON",
"OR",
"ORDER BY",
"THEN",
"WHEN",
"WHERE",
}
)
func NewDefaultCompleter(opts ...Option) readline.AutoCompleter {
c := completer{
// an empty struct satisfies the metadata.Reader interface, because it is actually empty
reader: struct{}{},
logger: log.New(os.Stdout, "ERROR: ", log.LstdFlags),
sqlStartCommands: CommonSqlStartCommands,
// TODO do we need to add built-in functions like, COALESCE, CAST, NULLIF, CONCAT etc?
sqlCommands: CommonSqlCommands,
backslashCommands: []string{
`\!`,
`\?`,
`\C`,
`\H`,
`\T`,
`\Z`,
`\a`,
`\begin`,
`\bind`,
`\c`,
`\cd`,
`\commit`,
`\connect`,
`\conninfo`,
`\copy`,
`\copyright`,
`\cset`,
`\d+`,
`\dS+`,
`\dS`,
`\da+`,
`\daS+`,
`\daS`,
`\da`,
`\df+`,
`\dfS+`,
`\dfS`,
`\df`,
`\di+`,
`\diS+`,
`\diS`,
`\di`,
`\dm+`,
`\dmS+`,
`\dmS`,
`\dm`,
`\dn+`,
`\dnS+`,
`\dnS`,
`\dn`,
`\drivers`,
`\ds+`,
`\dsS+`,
`\dsS`,
`\ds`,
`\dt+`,
`\dtS+`,
`\dtS`,
`\dt`,
`\dv+`,
`\dvS+`,
`\dvS`,
`\dv`,
`\e`,
`\echo`,
`\f`,
`\g`,
`\getenv`,
`\gexec`,
`\gset`,
`\gx`,
`\i`,
`\ir`,
`\l+`,
`\l`,
`\p`,
`\password`,
`\prompt`,
`\pset`,
`\q`,
`\r`,
`\raw`,
`\rollback`,
`\set`,
`\setenv`,
`\t`,
`\timing`,
`\unset`,
`\w`,
`\watch`,
`\x`,
},
}
for _, o := range opts {
o(&c)
}
return c
}
// Option to configure the reader
type Option func(*completer)
// WithDB option
func WithDB(db metadata.DB) Option {
return func(c *completer) {
c.db = db
}
}
// WithReader option
func WithReader(r metadata.Reader) Option {
return func(c *completer) {
c.reader = r
}
}
// WithLogger option
func WithLogger(l logger) Option {
return func(c *completer) {
c.logger = l
}
}
// WithSQLStartCommands that can begin a query
func WithSQLStartCommands(commands []string) Option {
return func(c *completer) {
c.sqlStartCommands = commands
}
}
// WithSQLCommands that can be any part of a query
func WithSQLCommands(commands []string) Option {
return func(c *completer) {
c.sqlCommands = commands
}
}
// WithConnStrings option
func WithConnStrings(connStrings []string) Option {
return func(c *completer) {
c.connStrings = connStrings
}
}
// WithBeforeComplete option
func WithBeforeComplete(f CompleteFunc) Option {
return func(c *completer) {
c.beforeComplete = f
}
}
// completer based on https://github.com/postgres/postgres/blob/9f3665fbfc34b963933e51778c7feaa8134ac885/src/bin/psql/tab-complete.c
type completer struct {
db metadata.DB
reader metadata.Reader
logger logger
sqlStartCommands []string
sqlCommands []string
backslashCommands []string
connStrings []string
beforeComplete CompleteFunc
}
// CompleteFunc returns patterns completing current text, using previous words as context
type CompleteFunc func(previousWords []string, text []rune) [][]rune
type logger interface {
Println(...interface{})
}
func (c completer) Do(line []rune, start int) (newLine [][]rune, length int) {
var i int
for i = start - 1; i > 0; i-- {
if strings.ContainsRune(WORD_BREAKS, line[i]) {
i++
break
}
}
if i == -1 {
i = 0
}
previousWords := getPreviousWords(start, line)
text := line[i:start]
if c.beforeComplete != nil {
result := c.beforeComplete(previousWords, text)
if result != nil {
return result, len(text)
}
}
result := c.complete(previousWords, text)
if result != nil {
return result, len(text)
}
return nil, 0
}
func (c completer) complete(previousWords []string, text []rune) [][]rune {
if len(text) > 0 {
if len(previousWords) == 0 && text[0] == '\\' {
/* If current word is a backslash command, offer completions for that */
return CompleteFromListCase(MATCH_CASE, text, c.backslashCommands...)
}
if text[0] == ':' {
if len(text) == 1 || text[1] == ':' {
return nil
}
/* If current word is a variable interpolation, handle that case */
if text[1] == '\'' {
return completeFromVariables(text, ":'", "'", true)
}
if text[1] == '"' {
return completeFromVariables(text, ":\"", "\"", true)
}
return completeFromVariables(text, ":", "", true)
}
}
if len(previousWords) == 0 {
/* If no previous word, suggest one of the basic sql commands */
return CompleteFromList(text, c.sqlStartCommands...)
}
/* DELETE --- can be inside EXPLAIN, RULE, etc */
/* ... despite which, only complete DELETE with FROM at start of line */
if matches(IGNORE_CASE, previousWords, "DELETE") {
return CompleteFromList(text, "FROM")
}
/* Complete DELETE FROM with a list of tables */
if TailMatches(IGNORE_CASE, previousWords, "DELETE", "FROM") {
return c.completeWithUpdatables(text)
}
/* Complete DELETE FROM */
if TailMatches(IGNORE_CASE, previousWords, "DELETE", "FROM", "*") {
return CompleteFromList(text, "USING", "WHERE")
}
/* XXX: implement tab completion for DELETE ... USING */
/* Complete CREATE */
if TailMatches(IGNORE_CASE, previousWords, "CREATE") {
return CompleteFromList(text, "DATABASE", "SCHEMA", "SEQUENCE", "TABLE", "VIEW", "TEMPORARY")
}
if TailMatches(IGNORE_CASE, previousWords, "CREATE", "TEMP|TEMPORARY") {
return CompleteFromList(text, "TABLE", "VIEW")
}
if TailMatches(IGNORE_CASE, previousWords, "CREATE", "TABLE", "*") || TailMatches(IGNORE_CASE, previousWords, "CREATE", "TEMP|TEMPORARY", "TABLE", "*") {
return CompleteFromList(text, "(")
}
/* INSERT --- can be inside EXPLAIN, RULE, etc */
/* Complete INSERT with "INTO" */
if TailMatches(IGNORE_CASE, previousWords, "INSERT") {
return CompleteFromList(text, "INTO")
}
/* Complete INSERT INTO with table names */
if TailMatches(IGNORE_CASE, previousWords, "INSERT", "INTO") {
return c.completeWithUpdatables(text)
}
/* Complete "INSERT INTO (" with attribute names */
if TailMatches(IGNORE_CASE, previousWords, "INSERT", "INTO", "*", "(") {
return c.completeWithAttributes(IGNORE_CASE, previousWords[1], text)
}
/*
* Complete INSERT INTO with "(" or "VALUES" or "SELECT" or
* "TABLE" or "DEFAULT VALUES" or "OVERRIDING"
*/
if TailMatches(IGNORE_CASE, previousWords, "INSERT", "INTO", "*") {
return CompleteFromList(text, "(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES", "OVERRIDING")
}
/*
* Complete INSERT INTO (attribs) with "VALUES" or "SELECT" or
* "TABLE" or "OVERRIDING"
*/
if TailMatches(IGNORE_CASE, previousWords, "INSERT", "INTO", "*", "*") &&
strings.HasSuffix(previousWords[0], ")") {
return CompleteFromList(text, "SELECT", "TABLE", "VALUES", "OVERRIDING")
}
/* Complete OVERRIDING */
if TailMatches(IGNORE_CASE, previousWords, "OVERRIDING") {
return CompleteFromList(text, "SYSTEM VALUE", "USER VALUE")
}
/* Complete after OVERRIDING clause */
if TailMatches(IGNORE_CASE, previousWords, "OVERRIDING", "*", "VALUE") {
return CompleteFromList(text, "SELECT", "TABLE", "VALUES")
}
/* Insert an open parenthesis after "VALUES" */
if TailMatches(IGNORE_CASE, previousWords, "VALUES") && !TailMatches(IGNORE_CASE, previousWords, "DEFAULT", "VALUES") {
return CompleteFromList(text, "(")
}
/* UPDATE --- can be inside EXPLAIN, RULE, etc */
/* If prev. word is UPDATE suggest a list of tables */
if TailMatches(IGNORE_CASE, previousWords, "UPDATE") {
return c.completeWithUpdatables(text)
}
/* Complete UPDATE with "SET" */
if TailMatches(IGNORE_CASE, previousWords, "UPDATE", "*") {
return CompleteFromList(text, "SET")
}
/* Complete UPDATE SET with list of attributes */
if TailMatches(IGNORE_CASE, previousWords, "UPDATE", "*", "SET") {
return c.completeWithAttributes(IGNORE_CASE, previousWords[1], text)
}
/* UPDATE SET = */
if TailMatches(IGNORE_CASE, previousWords, "UPDATE", "*", "SET", "!*=") {
return CompleteFromList(text, "=")
}
/* WHERE */
/* Simple case of the word before the where being the table name */
if TailMatches(IGNORE_CASE, previousWords, "*", "WHERE") {
// TODO would be great to _try_ to parse the (incomplete) query
// and get a list of possible selectables to filter by
return c.completeWithAttributes(IGNORE_CASE, previousWords[1], text,
"AND",
"OR",
"CASE",
"WHEN",
"THEN",
"ELSE",
"END",
)
}
/* ... FROM | JOIN ... */
if TailMatches(IGNORE_CASE, previousWords, "FROM|JOIN") {
return c.completeWithSelectables(text)
}
/* TABLE, but not TABLE embedded in other commands */
if matches(IGNORE_CASE, previousWords, "TABLE") {
return c.completeWithUpdatables(text)
}
/* Backslash commands */
if TailMatches(MATCH_CASE, previousWords, `\cd|\e|\edit|\g|\gx|\i|\include|\ir|\include_relative|\o|\out|\s|\w|\write`) {
return completeFromFiles(text)
}
if TailMatches(MATCH_CASE, previousWords, `\c|\connect|\copy`) ||
TailMatches(MATCH_CASE, previousWords, `\copy`, `*`) {
return CompleteFromList(text, c.connStrings...)
}
if TailMatches(MATCH_CASE, previousWords, `\copy`, `*`, `*`) {
return nil
}
if TailMatches(MATCH_CASE, previousWords, `\da*`) {
return c.completeWithFunctions(text, []string{"AGGREGATE"})
}
if TailMatches(MATCH_CASE, previousWords, `\df*`) {
return c.completeWithFunctions(text, []string{})
}
if TailMatches(MATCH_CASE, previousWords, `\di*`) {
return c.completeWithIndexes(text)
}
if TailMatches(MATCH_CASE, previousWords, `\dn*`) {
return c.completeWithSchemas(text)
}
if TailMatches(MATCH_CASE, previousWords, `\ds*`) {
return c.completeWithSequences(text)
}
if TailMatches(MATCH_CASE, previousWords, `\dt*`) {
return c.completeWithTables(text, []string{"TABLE", "BASE TABLE", "SYSTEM TABLE", "SYNONYM", "LOCAL TEMPORARY", "GLOBAL TEMPORARY"})
}
if TailMatches(MATCH_CASE, previousWords, `\dv*`) {
return c.completeWithTables(text, []string{"VIEW", "SYSTEM VIEW"})
}
if TailMatches(MATCH_CASE, previousWords, `\dm*`) {
return c.completeWithTables(text, []string{"MATERIALIZED VIEW"})
}
if TailMatches(MATCH_CASE, previousWords, `\d*`) {
return c.completeWithSelectables(text)
}
if TailMatches(MATCH_CASE, previousWords, `\l*`) ||
TailMatches(MATCH_CASE, previousWords, `\lo*`) {
return c.completeWithCatalogs(text)
}
if TailMatches(MATCH_CASE, previousWords, `\pset`) {
return CompleteFromList(text, `border`, `columns`, `expanded`, `fieldsep`, `fieldsep_zero`,
`footer`, `format`, `linestyle`, `null`, `numericlocale`, `pager`, `pager_min_lines`,
`recordsep`, `recordsep_zero`, `tableattr`, `title`, `title`, `tuples_only`,
`unicode_border_linestyle`, `unicode_column_linestyle`, `unicode_header_linestyle`)
}
if TailMatches(MATCH_CASE, previousWords, `\pset`, `expanded`) {
return CompleteFromList(text, "auto", "on", "off")
}
if TailMatches(MATCH_CASE, previousWords, `\pset`, `pager`) {
return CompleteFromList(text, "always", "on", "off")
}
if TailMatches(MATCH_CASE, previousWords, `\pset`, `fieldsep_zero|footer|numericlocale|pager|recordsep_zero|tuples_only`) {
return CompleteFromList(text, "on", "off")
}
if TailMatches(MATCH_CASE, previousWords, `\pset`, `format`) {
return CompleteFromList(text, "unaligned", "aligned", "wrapped", "html", "asciidoc", "latex", "latex-longtable", "troff-ms", "csv", "json", "vertical")
}
if TailMatches(MATCH_CASE, previousWords, `\pset`, `linestyle`) {
return CompleteFromList(text, "ascii", "old-ascii", "unicode")
}
if TailMatches(MATCH_CASE, previousWords, `\pset`, `unicode_border_linestyle|unicode_column_linestyle|unicode_header_linestyle`) {
return CompleteFromList(text, "single", "double")
}
if TailMatches(MATCH_CASE, previousWords, `\pset`, `*`) ||
TailMatches(MATCH_CASE, previousWords, `\pset`, `*`, `*`) {
return nil
}
if TailMatches(MATCH_CASE, previousWords, `\?`) {
return CompleteFromList(text, "commands", "options", "variables")
}
// is suggesting basic sql commands better than nothing?
return CompleteFromList(text, c.sqlCommands...)
}
func getPreviousWords(point int, buf []rune) []string {
var i int
/*
* Allocate a slice of strings (rune slices). The worst case is that the line contains only
* non-whitespace WORD_BREAKS characters, making each one a separate word.
* This is usually much more space than we need, but it's cheaper than
* doing a separate malloc() for each word.
*/
previousWords := make([]string, 0, point*2)
/*
* First we look for a non-word char before the current point. (This is
* probably useless, if readline is on the same page as we are about what
* is a word, but if so it's cheap.)
*/
for i = point - 1; i >= 0; i-- {
if strings.ContainsRune(WORD_BREAKS, buf[i]) {
break
}
}
point = i
/*
* Now parse words, working backwards, until we hit start of line. The
* backwards scan has some interesting but intentional properties
* concerning parenthesis handling.
*/
for point >= 0 {
var start, end int
inquotes := false
parentheses := 0
/* now find the first non-space which then constitutes the end */
end = -1
for i = point; i >= 0; i-- {
if !unicode.IsSpace(buf[i]) {
end = i
break
}
}
/* if no end found, we're done */
if end < 0 {
break
}
/*
* Otherwise we now look for the start. The start is either the last
* character before any word-break character going backwards from the
* end, or it's simply character 0. We also handle open quotes and
* parentheses.
*/
for start = end; start > 0; start-- {
if buf[start] == '"' {
inquotes = !inquotes
}
if inquotes {
continue
}
if buf[start] == ')' {
parentheses++
} else if buf[start] == '(' {
parentheses -= 1
if parentheses <= 0 {
break
}
} else if parentheses == 0 && strings.ContainsRune(WORD_BREAKS, buf[start-1]) {
break
}
}
/* Return the word located at start to end inclusive */
i = end - start + 1
previousWords = append(previousWords, string(buf[start:start+i]))
/* Continue searching */
point = start - 1
}
return previousWords
}
// TailMatches when last words match all patterns
func TailMatches(ct caseType, words []string, patterns ...string) bool {
if len(words) < len(patterns) {
return false
}
for i, p := range patterns {
if !wordMatches(ct, p, words[len(patterns)-i-1]) {
return false
}
}
return true
}
func matches(ct caseType, words []string, patterns ...string) bool {
if len(words) != len(patterns) {
return false
}
for i, p := range patterns {
if !wordMatches(ct, p, words[len(patterns)-i-1]) {
return false
}
}
return true
}
func wordMatches(ct caseType, pattern, word string) bool {
if pattern == "*" {
return true
}
if pattern[0] == '!' {
return !wordMatches(ct, pattern[1:], word)
}
cmp := func(a, b string) bool { return a == b }
if ct == IGNORE_CASE {
cmp = strings.EqualFold
}
for _, p := range strings.Split(pattern, "|") {
star := strings.IndexByte(p, '*')
if star == -1 {
if cmp(p, word) {
return true
}
} else {
if len(word) >= len(p)-1 && cmp(p[0:star], word[0:star]) && (star >= len(p) || cmp(p[star+1:], word[len(word)-len(p)+star+1:])) {
return true
}
}
}
return false
}
// CompleteFromList where items starts with text, ignoring case
func CompleteFromList(text []rune, options ...string) [][]rune {
return CompleteFromListCase(IGNORE_CASE, text, options...)
}
// CompleteFromList where items starts with text
func CompleteFromListCase(ct caseType, text []rune, options ...string) [][]rune {
if len(options) == 0 {
return nil
}
isLower := false
if len(text) > 0 {
isLower = unicode.IsLower(text[0])
}
prefix := string(text)
if ct == IGNORE_CASE {
prefix = strings.ToUpper(prefix)
}
result := make([][]rune, 0, len(options))
for _, o := range options {
if (ct == IGNORE_CASE && !strings.HasPrefix(strings.ToUpper(o), prefix)) ||
(ct == MATCH_CASE && !strings.HasPrefix(o, prefix)) {
continue
}
match := o[len(text):]
if ct == IGNORE_CASE && isLower {
match = strings.ToLower(match)
}
result = append(result, []rune(match))
}
return result
}
func completeFromVariables(text []rune, prefix, suffix string, needValue bool) [][]rune {
vars := env.Vars().Vars()
names := make([]string, 0, len(vars))
for name, value := range vars {
if needValue && value == "" {
continue
}
names = append(names, prefix+name+suffix)
}
return CompleteFromListCase(MATCH_CASE, text, names...)
}
func (c completer) completeWithSelectables(text []rune) [][]rune {
filter := parseIdentifier(string(text))
names := c.getNamespaces(filter)
if r, ok := c.reader.(metadata.TableReader); ok {
tables := c.getNames(
func() (iterator, error) {
return r.Tables(filter)
},
func(res interface{}) string {
t := res.(*metadata.TableSet).Get()
return qualifiedIdentifier(filter, t.Catalog, t.Schema, t.Name)
},
)
names = append(names, tables...)
}
if r, ok := c.reader.(metadata.FunctionReader); ok {
functions := c.getNames(
func() (iterator, error) {
return r.Functions(filter)
},
func(res interface{}) string {
f := res.(*metadata.FunctionSet).Get()
return qualifiedIdentifier(filter, f.Catalog, f.Schema, f.Name)
},
)
names = append(names, functions...)
}
if r, ok := c.reader.(metadata.SequenceReader); ok {
sequences := c.getNames(
func() (iterator, error) {
return r.Sequences(filter)
},
func(res interface{}) string {
s := res.(*metadata.SequenceSet).Get()
return qualifiedIdentifier(filter, s.Catalog, s.Schema, s.Name)
},
)
names = append(names, sequences...)
}
sort.Strings(names)
// TODO make sure CompleteFromList would properly handle quoted identifiers
return CompleteFromList(text, names...)
}
func (c completer) completeWithTables(text []rune, types []string) [][]rune {
r, ok := c.reader.(metadata.TableReader)
if !ok {
return [][]rune{}
}
filter := parseIdentifier(string(text))
filter.Types = types
names := c.getNamespaces(filter)
tables := c.getNames(
func() (iterator, error) {
return r.Tables(filter)
},
func(res interface{}) string {
t := res.(*metadata.TableSet).Get()
return qualifiedIdentifier(filter, t.Catalog, t.Schema, t.Name)
},
)
names = append(names, tables...)
sort.Strings(names)
return CompleteFromList(text, names...)
}
func (c completer) completeWithFunctions(text []rune, types []string) [][]rune {
r, ok := c.reader.(metadata.FunctionReader)
if !ok {
return [][]rune{}
}
filter := parseIdentifier(string(text))
filter.Types = types
names := c.getNamespaces(filter)
functions := c.getNames(
func() (iterator, error) {
return r.Functions(filter)
},
func(res interface{}) string {
f := res.(*metadata.FunctionSet).Get()
return qualifiedIdentifier(filter, f.Catalog, f.Schema, f.Name)
},
)
names = append(names, functions...)
sort.Strings(names)
return CompleteFromList(text, names...)
}
func (c completer) completeWithIndexes(text []rune) [][]rune {
r, ok := c.reader.(metadata.IndexReader)
if !ok {
return [][]rune{}
}
filter := parseIdentifier(string(text))
names := c.getNamespaces(filter)
indexes := c.getNames(
func() (iterator, error) {
return r.Indexes(filter)
},
func(res interface{}) string {
f := res.(*metadata.IndexSet).Get()
return qualifiedIdentifier(filter, f.Catalog, f.Schema, f.Name)
},
)
names = append(names, indexes...)
sort.Strings(names)
return CompleteFromList(text, names...)
}
func (c completer) completeWithSequences(text []rune) [][]rune {
r, ok := c.reader.(metadata.SequenceReader)
if !ok {
return [][]rune{}
}
filter := parseIdentifier(string(text))
names := c.getNamespaces(filter)
sequences := c.getNames(
func() (iterator, error) {
return r.Sequences(filter)
},
func(res interface{}) string {
s := res.(*metadata.SequenceSet).Get()
return qualifiedIdentifier(filter, s.Catalog, s.Schema, s.Name)
},
)
names = append(names, sequences...)
sort.Strings(names)
return CompleteFromList(text, names...)
}
func (c completer) completeWithSchemas(text []rune) [][]rune {
r, ok := c.reader.(metadata.SchemaReader)
if !ok {
return [][]rune{}
}
filter := parseIdentifier(string(text))
names := c.getNames(
func() (iterator, error) {
if filter.Schema != "" {
// name should already have a wildcard appended
return r.Schemas(metadata.Filter{Catalog: filter.Schema, Name: filter.Name, WithSystem: true})
}
return r.Schemas(filter)
},
func(res interface{}) string {
s := res.(*metadata.SchemaSet).Get()
return qualifiedIdentifier(filter, "", s.Catalog, s.Schema)
},
)
return CompleteFromList(text, names...)
}
func (c completer) completeWithCatalogs(text []rune) [][]rune {
r, ok := c.reader.(metadata.CatalogReader)
if !ok {
return [][]rune{}
}
filter := parseIdentifier(string(text))
names := c.getNames(
func() (iterator, error) {
return r.Catalogs(filter)
},
func(res interface{}) string {
s := res.(*metadata.CatalogSet).Get()
return s.Catalog
},
)
return CompleteFromList(text, names...)
}
func (c completer) completeWithUpdatables(text []rune) [][]rune {
filter := parseIdentifier(string(text))
names := c.getNamespaces(filter)
if r, ok := c.reader.(metadata.TableReader); ok {
// exclude materialized views, sequences, system tables, synonyms
filter.Types = []string{"TABLE", "BASE TABLE", "LOCAL TEMPORARY", "GLOBAL TEMPORARY", "VIEW"}
tables := c.getNames(
func() (iterator, error) {
return r.Tables(filter)
},
func(res interface{}) string {
t := res.(*metadata.TableSet).Get()
return qualifiedIdentifier(filter, t.Catalog, t.Schema, t.Name)
},
)
names = append(names, tables...)
}
sort.Strings(names)
// TODO make sure CompleteFromList would properly handle quoted identifiers
return CompleteFromList(text, names...)
}
func (c completer) getNamespaces(f metadata.Filter) []string {
names := make([]string, 0, 10)
if f.Catalog == "" && f.Schema == "" {
if r, ok := c.reader.(metadata.CatalogReader); ok {
catalogs := c.getNames(
func() (iterator, error) { return r.Catalogs(metadata.Filter{}) },
func(res interface{}) string {
return res.(*metadata.CatalogSet).Get().Catalog
},
)
names = append(names, catalogs...)
}
}
if f.Catalog != "" {
// filter is already fully qualified, so don't return any namespaces
return names
}
if r, ok := c.reader.(metadata.SchemaReader); ok {
schemas := c.getNames(
func() (iterator, error) {
if f.Schema != "" {
// name should already have a wildcard appended
return r.Schemas(metadata.Filter{Catalog: f.Schema, Name: f.Name, WithSystem: true})
}
return r.Schemas(f)
},
func(res interface{}) string {
s := res.(*metadata.SchemaSet).Get()
return qualifiedIdentifier(f, "", s.Catalog, s.Schema)
},
)
names = append(names, schemas...)
}
return names
}
func (c completer) completeWithAttributes(_ caseType, selectable string, text []rune, options ...string) [][]rune {
names := make([]string, 0, 10)
if r, ok := c.reader.(metadata.ColumnReader); ok {
parent := parseParentIdentifier(selectable)
columns := c.getNames(
func() (iterator, error) {
return r.Columns(parent)
},
func(res interface{}) string {
return res.(*metadata.ColumnSet).Get().Name
},
)
names = append(names, columns...)
}
if r, ok := c.reader.(metadata.FunctionReader); ok {
filter := parseIdentifier(string(text))
// functions don't have to be fully qualified to be callable
filter.OnlyVisible = false
functions := c.getNames(
func() (iterator, error) {
return r.Functions(filter)
},
func(res interface{}) string {
return res.(*metadata.FunctionSet).Get().Name
},
)
names = append(names, functions...)
}
names = append(names, options...)
return CompleteFromList(text, names...)
}
// parseIdentifier into catalog, schema and name
func parseIdentifier(name string) metadata.Filter {
// TODO handle quoted identifiers
result := metadata.Filter{}
if !strings.ContainsRune(name, '.') {
result.Name = name + "%"
result.OnlyVisible = true
} else {
parts := strings.SplitN(name, ".", 3)
if len(parts) == 2 {
result.Schema = parts[0]
result.Name = parts[1] + "%"
} else {
result.Catalog = parts[0]
result.Schema = parts[1]
result.Name = parts[2] + "%"
}
}
if result.Schema != "" || len(result.Name) > 3 {
result.WithSystem = true
}
return result
}
// parseParentIdentifier into catalog, schema and parent
func parseParentIdentifier(name string) metadata.Filter {
// TODO handle quoted identifiers
result := metadata.Filter{}
if !strings.ContainsRune(name, '.') {
result.Parent = name
result.OnlyVisible = true
} else {
parts := strings.SplitN(name, ".", 3)
if len(parts) == 2 {
result.Schema = parts[0]
result.Parent = parts[1]
} else {
result.Catalog = parts[0]
result.Schema = parts[1]
result.Parent = parts[2]
}
}
if result.Schema != "" {
result.WithSystem = true
}
return result
}
func qualifiedIdentifier(filter metadata.Filter, catalog, schema, name string) string {
// TODO handle quoted identifiers
if filter.Catalog != "" && filter.Schema != "" {
return catalog + "." + schema + "." + name
}
if filter.Schema != "" {
return schema + "." + name
}
return name
}
func (c completer) getNames(query func() (iterator, error), mapper func(interface{}) string) []string {
res, err := query()
if err != nil {
if err != text.ErrNotSupported {
c.logger.Println("Error getting selectables", err)
}
return nil
}
defer res.Close()
// there can be duplicates if names are not qualified
values := make(map[string]struct{}, 10)
for res.Next() {
values[mapper(res)] = struct{}{}
}
result := make([]string, 0, len(values))
for v := range values {
result = append(result, v)
}
return result
}
type iterator interface {
Next() bool
Close() error
}
func completeFromFiles(text []rune) [][]rune {
// TODO handle quotes properly
dir := filepath.Dir(string(text))
dirs, err := os.ReadDir(dir)
if err != nil {
return nil
}
matches := make([]string, 0, len(dirs))
switch dir {
case ".":
dir = ""
case "/":
// pass
default:
dir += "/"
}
for _, entry := range dirs {
name := entry.Name()
if entry.IsDir() {
name += "/"
}
matches = append(matches, dir+name)
}
return CompleteFromList(text, matches...)
}
================================================
FILE: drivers/completer/completer_test.go
================================================
package completer
import (
"testing"
"github.com/xo/usql/drivers/metadata"
)
func TestCompleter(t *testing.T) {
cases := []struct {
name string
line string
start int
expSuggestions []string
expLength int
}{
{
"Single SQL keyword, uppercase",
"SEL",
3,
[]string{
"ECT",
},
3,
},
{
"Single SQL keyword, lowercase",
"ex",
2,
[]string{
"ec",
"ecute",
"plain",
},
2,
},
{
"usql command",
`\dt`,
3,
[]string{
`+`,
``,
`S+`,
`S`,
},
3,
},
{
"files",
`\i comp`,
7,
[]string{
"leter.go",
"leter_test.go",
},
4,
},
{
"connections",
`\c p`,
4,
[]string{
"g://",
},
1,
},
{
"3rd word",
"SELECT * F",
10,
[]string{
"ULL OUTER JOIN",
"ROM",
"ETCH",
},
1,
},
{
"Selectables",
"SELECT * FROM ",
14,
[]string{
"main",
"remote",
"default",
"system",
"film",
"factory",
},
0,
},
{
"Namespaced with catalog",
"SELECT * FROM remote.",
21,
[]string{
"film",
"factory",
},
7,
},
{
"Namespaced with schema",
"SELECT * FROM system.",
21,
[]string{
"film",
"factory",
},
7,
},
{
"Namespaced with catalog.schema",
"SELECT * FROM remote.default.f",
30,
[]string{
"ilm",
"actory",
},
16,
},
{
"Attributes",
"SELECT * FROM film WHERE ",
25,
[]string{
"id",
"name",
"CASE",
"AND",
"OR",
"WHEN",
"THEN",
"ELSE",
"END",
},
0,
},
{
"insert",
"INS",
3,
[]string{
"ERT",
},
3,
},
{
"insert into",
"INSERT IN",
9,
[]string{
"TO",
},
2,
},
{
"insert into table",
"INSERT INTO fi",
14,
[]string{
"lm",
},
2,
},
{
"insert into table select from",
"INSERT INTO film SE",
19,
[]string{
"LECT",
},
2,
},
{
"insert into table attrs",
"INSERT INTO film (",
18,
[]string{
"id",
"name",
},
0,
},
{
"insert into table values",
"INSERT INTO film (a)",
20,
[]string{
"SELECT",
"TABLE",
"VALUES",
"OVERRIDING",
},
0,
},
{
"update table set attrs",
"UPDATE film SET ",
16,
[]string{
"id",
"name",
},
0,
},
{
"update table set",
"update film set name ",
21,
[]string{
"=",
},
0,
},
{
"variables",
":a",
2,
[]string{},
2,
},
{
"type on create",
"CREATE ",
7,
[]string{
"SCHEMA",
"DATABASE",
"TABLE",
"SEQUENCE",
"VIEW",
"TEMPORARY",
},
0,
},
{
"brackets on create table",
"CREATE TABLE p ",
15,
[]string{
"(",
},
0,
},
{
"TABLE Selectables",
"TABLE ",
6,
[]string{
"main",
"remote",
"default",
"system",
"film",
"factory",
},
0,
},
{
"TABLE namespaced with catalog",
"TABLE remote.",
13,
[]string{
"film",
"factory",
},
7,
},
{
"TABLE namespaced with schema",
"TABLE system.",
13,
[]string{
"film",
"factory",
},
7,
},
{
"TABLE namespaced with catalog.schema",
"TABLE remote.default.f",
22,
[]string{
"ilm",
"actory",
},
16,
},
}
completer := NewDefaultCompleter(WithReader(mockReader{}), WithConnStrings([]string{"pg://"}))
for _, test := range cases {
t.Run(test.name, func(t *testing.T) {
suggestions, length := completer.Do([]rune(test.line), test.start)
// need at least 2 pairs of nested loops, one for what's missing, second for what's extra
for _, exp := range test.expSuggestions {
found := false
for _, act := range suggestions {
if string(act) == exp {
found = true
break
}
}
if !found {
t.Errorf("Missing expected suggestion: %s", exp)
}
}
for _, act := range suggestions {
found := false
for _, exp := range test.expSuggestions {
if string(act) == exp {
found = true
break
}
}
if !found {
t.Errorf("Unexpected suggestion: %s", string(act))
}
}
if length != test.expLength {
t.Errorf("Expected Do() to return length %d, got %d", test.expLength, length)
}
})
}
}
type mockReader struct{}
var _ metadata.CatalogReader = &mockReader{}
var _ metadata.BasicReader = &mockReader{}
func (r mockReader) Catalogs(metadata.Filter) (*metadata.CatalogSet, error) {
return metadata.NewCatalogSet([]metadata.Catalog{
{
Catalog: "main",
},
{
Catalog: "remote",
},
}), nil
}
func (r mockReader) Schemas(metadata.Filter) (*metadata.SchemaSet, error) {
return metadata.NewSchemaSet([]metadata.Schema{
{
Schema: "default",
Catalog: "main",
},
{
Schema: "system",
Catalog: "main",
},
}), nil
}
func (r mockReader) Tables(f metadata.Filter) (*metadata.TableSet, error) {
return metadata.NewTableSet([]metadata.Table{
{
Catalog: f.Catalog,
Schema: f.Schema,
Name: "film",
},
{
Catalog: f.Catalog,
Schema: f.Schema,
Name: "factory",
},
}), nil
}
func (r mockReader) Columns(f metadata.Filter) (*metadata.ColumnSet, error) {
if f.Parent == "film" {
return metadata.NewColumnSet([]metadata.Column{
{
Name: "id",
},
{
Name: "name",
},
}), nil
}
return metadata.NewColumnSet([]metadata.Column{
{
Name: f.Catalog,
},
{
Name: f.Schema,
},
{
Name: f.Name,
},
}), nil
}
================================================
FILE: drivers/cosmos/cosmos.go
================================================
// Package cosmos defines and registers usql's Azure CosmosDB driver.
//
// See: https://github.com/btnguyen2k/gocosmos
package cosmos
import (
_ "github.com/btnguyen2k/gocosmos" // DRIVER
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("cosmos", drivers.Driver{
Process: drivers.StripTrailingSemicolon,
}, "gocosmos")
}
================================================
FILE: drivers/couchbase/couchbase.go
================================================
// Package couchbase defines and registers usql's Couchbase driver.
//
// See: https://github.com/couchbase/go_n1ql
package couchbase
import (
"context"
"strconv"
"strings"
_ "github.com/couchbase/go_n1ql" // DRIVER: n1ql
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("n1ql", drivers.Driver{
AllowMultilineComments: true,
Version: func(ctx context.Context, db drivers.DB) (string, error) {
ver := ""
/*
var buf []byte
if err := db.QueryRowContext(ctx, `SELECT ds_version() AS version`).Scan(&buf); err == nil {
var m map[string]string
if err := json.Unmarshal(buf, &m); err == nil {
if s, ok := m["version"]; ok {
ver = s
}
}
}
*/
var v string
if err := db.QueryRowContext(ctx, `SELECT RAW ds_version()`).Scan(&v); err == nil {
if s, err := strconv.Unquote(v); err == nil {
ver = s
}
}
return "Couchbase " + ver, nil
},
Err: func(err error) (string, string) {
return "", strings.TrimPrefix(err.Error(), "N1QL: ")
},
})
}
================================================
FILE: drivers/csvq/csvq.go
================================================
// Package csvq defines and registers usql's CSVQ driver.
//
// See: https://github.com/mithrandie/csvq-driver
// Group: base
package csvq
import (
"context"
"os"
"strings"
"github.com/mithrandie/csvq-driver" // DRIVER
"github.com/mithrandie/csvq/lib/query"
"github.com/xo/dburl"
"github.com/xo/usql/drivers"
)
func init() {
csvq.SetStdout(query.NewDiscard())
drivers.Register("csvq", drivers.Driver{
AllowMultilineComments: true,
Process: func(_ *dburl.URL, prefix string, sqlstr string) (string, string, bool, error) {
typ, q := drivers.QueryExecType(prefix, sqlstr)
if strings.HasPrefix(prefix, "SHOW") {
csvq.SetStdout(os.Stdout)
q = false
}
return typ, sqlstr, q, nil
},
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
if err := db.QueryRowContext(ctx, `SELECT @#VERSION`).Scan(&ver); err != nil {
return "", err
}
return "CSVQ " + ver, nil
},
Copy: drivers.CopyWithInsert(func(int) string { return "?" }),
})
}
================================================
FILE: drivers/databend/databend.go
================================================
// Package databend defines and registers usql's Databend driver.
//
// See: https://github.com/datafuselabs/databend-go
package databend
import (
"io"
_ "github.com/datafuselabs/databend-go" // DRIVER
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
infos "github.com/xo/usql/drivers/metadata/informationschema"
)
func init() {
newReader := infos.New(
infos.WithPlaceholder(func(int) string { return "?" }),
infos.WithCustomClauses(map[infos.ClauseName]string{
infos.SequenceColumnsIncrement: "''",
}),
infos.WithFunctions(false),
infos.WithIndexes(false),
infos.WithConstraints(false),
infos.WithColumnPrivileges(false),
)
drivers.Register("databend", drivers.Driver{
UseColumnTypes: true,
NewMetadataReader: newReader,
NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer {
return metadata.NewDefaultWriter(newReader(db, opts...))(db, w)
},
})
}
================================================
FILE: drivers/databricks/databricks.go
================================================
// Package databricks defines and registers usql's Databricks driver.
//
// See: https://github.com/databricks/databricks-sql-go
package databricks
import (
"errors"
_ "github.com/databricks/databricks-sql-go" // DRIVER
dberrs "github.com/databricks/databricks-sql-go/errors"
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("databricks", drivers.Driver{
Err: func(err error) (string, string) {
var e dberrs.DBExecutionError
if errors.As(err, &e) {
return e.SqlState(), e.Error()
}
return "", err.Error()
},
})
}
================================================
FILE: drivers/drivers.go
================================================
// Package drivers handles the registration, default implementation, and
// handles hooks for usql database drivers.
package drivers
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"io"
"reflect"
"regexp"
"strings"
"time"
"unicode"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/gohxs/readline"
"github.com/xo/dburl"
"github.com/xo/usql/drivers/completer"
"github.com/xo/usql/drivers/metadata"
"github.com/xo/usql/stmt"
"github.com/xo/usql/text"
)
// DB is the common interface for database operations, compatible with
// database/sql.DB and database/sql.Tx.
type DB interface {
Exec(string, ...interface{}) (sql.Result, error)
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
Query(string, ...interface{}) (*sql.Rows, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRow(string, ...interface{}) *sql.Row
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
Prepare(string) (*sql.Stmt, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
}
// Driver holds funcs for a driver.
type Driver struct {
// Name is a name to override the driver name with.
Name string
// AllowDollar will be passed to query buffers to enable dollar ($$) style
// strings.
AllowDollar bool
// AllowMultilineComments will be passed to query buffers to enable
// multiline (/**/) style comments.
AllowMultilineComments bool
// AllowCComments will be passed to query buffers to enable C (//) style
// comments.
AllowCComments bool
// AllowHashComments will be passed to query buffers to enable hash (#)
// style comments.
AllowHashComments bool
// RequirePreviousPassword will be used by RequirePreviousPassword.
RequirePreviousPassword bool
// LexerName is the name of the syntax lexer to use.
LexerName string
// LowerColumnNames will cause column names to be lowered cased.
LowerColumnNames bool
// UseColumnTypes will cause database's ColumnTypes func to be used for
// types.
UseColumnTypes bool
// ForceParams will be used to force parameters if defined.
ForceParams func(*dburl.URL)
// Open will be used by Open if defined.
Open func(context.Context, *dburl.URL, func() io.Writer, func() io.Writer) (func(string, string) (*sql.DB, error), error)
// Version will be used by Version if defined.
Version func(context.Context, DB) (string, error)
// User will be used by User if defined.
User func(context.Context, DB) (string, error)
// ChangePassword will be used by ChangePassword if defined.
ChangePassword func(DB, string, string, string) error
// IsPasswordErr will be used by IsPasswordErr if defined.
IsPasswordErr func(error) bool
// Process will be used by Process if defined.
Process func(*dburl.URL, string, string) (string, string, bool, error)
// ColumnTypes is a callback that will be used if
ColumnTypes func(*sql.ColumnType) (interface{}, error)
// RowsAffected will be used by RowsAffected if defined.
RowsAffected func(sql.Result) (int64, error)
// Err will be used by Error.Error if defined.
Err func(error) (string, string)
// ConvertBytes will be used by ConvertBytes to convert a raw []byte
// slice to a string if defined.
ConvertBytes func([]byte, string) (string, error)
// ConvertMap will be used by ConvertMap to convert a map[string]interface{}
// to a string if defined.
ConvertMap func(map[string]interface{}) (string, error)
// ConvertSlice will be used by ConvertSlice to convert a []interface{} to
// a string if defined.
ConvertSlice func([]interface{}) (string, error)
// ConvertDefault will be used by ConvertDefault to convert a interface{}
// to a string if defined.
ConvertDefault func(interface{}) (string, error)
// BatchAsTransaction will cause batched queries to be done in a
// transaction block.
BatchAsTransaction bool
// BatchQueryPrefixes will be used by BatchQueryPrefixes if defined.
BatchQueryPrefixes map[string]string
// NewMetadataReader returns a db metadata introspector.
NewMetadataReader func(db DB, opts ...metadata.ReaderOption) metadata.Reader
// NewMetadataWriter returns a db metadata printer.
NewMetadataWriter func(db DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer
// NewCompleter returns a db auto-completer.
NewCompleter func(db DB, opts ...completer.Option) readline.AutoCompleter
// Copy rows into the database table
Copy func(ctx context.Context, db *sql.DB, rows *sql.Rows, table string) (int64, error)
}
// drivers are registered drivers.
var drivers = make(map[string]Driver)
// Available returns the available drivers.
func Available() map[string]Driver {
return drivers
}
// Register registers driver d with name and associated aliases.
func Register(name string, d Driver, aliases ...string) {
if _, ok := drivers[name]; ok {
panic(fmt.Sprintf("driver %s is already registered", name))
}
drivers[name] = d
for _, alias := range aliases {
if _, ok := drivers[alias]; ok {
panic(fmt.Sprintf("alias %s is already registered", name))
}
drivers[alias] = d
}
}
// Registered returns whether or not a driver is registered.
func Registered(name string) bool {
_, ok := drivers[name]
return ok
}
// LowerColumnNames returns whether or not column names should be converted to
// lower case for a driver.
func LowerColumnNames(u *dburl.URL) bool {
if d, ok := drivers[u.Driver]; ok {
return d.LowerColumnNames
}
return false
}
// UseColumnTypes returns whether or not a driver should uses column types.
func UseColumnTypes(u *dburl.URL) bool {
if d, ok := drivers[u.Driver]; ok {
return d.UseColumnTypes
}
return false
}
// ForceParams forces parameters on the DSN for a driver.
func ForceParams(u *dburl.URL) {
d, ok := drivers[u.Driver]
if ok && d.ForceParams != nil {
d.ForceParams(u)
}
}
// Open opens a sql.DB connection for a driver.
func Open(ctx context.Context, u *dburl.URL, stdout, stderr func() io.Writer) (*sql.DB, error) {
d, ok := drivers[u.Driver]
if !ok {
return nil, WrapErr(u.Driver, text.ErrDriverNotAvailable)
}
f := sql.Open
if d.Open != nil {
var err error
if f, err = d.Open(ctx, u, stdout, stderr); err != nil {
return nil, WrapErr(u.Driver, err)
}
}
driver := u.Driver
if u.GoDriver != "" {
driver = u.GoDriver
}
db, err := f(driver, u.DSN)
if err != nil {
return nil, WrapErr(u.Driver, err)
}
return db, nil
}
// stmtOpts returns statement options for a driver.
func stmtOpts(u *dburl.URL) []stmt.Option {
if u != nil {
if d, ok := drivers[u.Driver]; ok {
return []stmt.Option{
stmt.WithAllowDollar(d.AllowDollar),
stmt.WithAllowMultilineComments(d.AllowMultilineComments),
stmt.WithAllowCComments(d.AllowCComments),
stmt.WithAllowHashComments(d.AllowHashComments),
}
}
}
return []stmt.Option{
stmt.WithAllowDollar(true),
stmt.WithAllowMultilineComments(true),
stmt.WithAllowCComments(true),
stmt.WithAllowHashComments(true),
}
}
// NewStmt wraps creating a new stmt.Stmt for a driver.
func NewStmt(u *dburl.URL, f func() ([]rune, error), opts ...stmt.Option) *stmt.Stmt {
return stmt.New(f, append(opts, stmtOpts(u)...)...)
}
// ConfigStmt sets the stmt.Stmt options for a driver.
func ConfigStmt(u *dburl.URL, s *stmt.Stmt) {
if u == nil {
return
}
for _, o := range stmtOpts(u) {
o(s)
}
}
// Version returns information about the database connection for a driver.
func Version(ctx context.Context, u *dburl.URL, db DB) (string, error) {
if d, ok := drivers[u.Driver]; ok && d.Version != nil {
ver, err := d.Version(ctx, db)
return ver, WrapErr(u.Driver, err)
}
var ver string
err := db.QueryRowContext(ctx, `SELECT version();`).Scan(&ver)
if err != nil || ver == "" {
ver = ""
}
return ver, nil
}
// User returns the current database user for a driver.
func User(ctx context.Context, u *dburl.URL, db DB) (string, error) {
if d, ok := drivers[u.Driver]; ok && d.User != nil {
user, err := d.User(ctx, db)
return user, WrapErr(u.Driver, err)
}
var user string
_ = db.QueryRowContext(ctx, `SELECT current_user`).Scan(&user)
return user, nil
}
// Process processes the sql query for a driver.
func Process(u *dburl.URL, prefix, sqlstr string) (string, string, bool, error) {
if d, ok := drivers[u.Driver]; ok && d.Process != nil {
a, b, c, err := d.Process(u, prefix, sqlstr)
return a, b, c, WrapErr(u.Driver, err)
}
typ, q := QueryExecType(prefix, sqlstr)
return typ, sqlstr, q, nil
}
// ColumnTypes returns the column types callback for a driver.
func ColumnTypes(u *dburl.URL) func(*sql.ColumnType) (interface{}, error) {
return drivers[u.Driver].ColumnTypes
}
// IsPasswordErr returns true if an err is a password error for a driver.
func IsPasswordErr(u *dburl.URL, err error) bool {
drv := u.Driver
if e, ok := err.(*Error); ok {
drv, err = e.Driver, e.Err
}
if d, ok := drivers[drv]; ok && d.IsPasswordErr != nil {
return d.IsPasswordErr(err)
}
return false
}
// RequirePreviousPassword returns true if a driver requires a previous
// password when changing a user's password.
func RequirePreviousPassword(u *dburl.URL) bool {
if d, ok := drivers[u.Driver]; ok {
return d.RequirePreviousPassword
}
return false
}
// CanChangePassword returns whether or not the a driver supports changing
// passwords.
func CanChangePassword(u *dburl.URL) error {
if d, ok := drivers[u.Driver]; ok && d.ChangePassword != nil {
return nil
}
return text.ErrPasswordNotSupportedByDriver
}
// ChangePassword initiates a user password change for the a driver. If user is
// not supplied, then the current user will be retrieved from User.
func ChangePassword(u *dburl.URL, db DB, user, new, old string) (string, error) {
if d, ok := drivers[u.Driver]; ok && d.ChangePassword != nil {
if user == "" {
var err error
if user, err = User(context.Background(), u, db); err != nil {
return "", err
}
}
return user, d.ChangePassword(db, user, new, old)
}
return "", text.ErrPasswordNotSupportedByDriver
}
// Columns returns the column names for the SQL row result for a driver.
func Columns(u *dburl.URL, rows *sql.Rows) ([]string, error) {
cols, err := rows.Columns()
if err != nil {
return nil, WrapErr(u.Driver, err)
}
if drivers[u.Driver].LowerColumnNames {
for i, s := range cols {
if j := strings.IndexFunc(s, func(r rune) bool {
return unicode.IsLetter(r) && unicode.IsLower(r)
}); j == -1 {
cols[i] = strings.ToLower(s)
}
}
}
for i, c := range cols {
if strings.TrimSpace(c) == "" {
cols[i] = fmt.Sprintf("col%d", i)
}
}
return cols, nil
}
// ConvertBytes returns a func to handle converting bytes for a driver.
func ConvertBytes(u *dburl.URL) func([]byte, string) (string, error) {
if d, ok := drivers[u.Driver]; ok && d.ConvertBytes != nil {
return d.ConvertBytes
}
return func(buf []byte, _ string) (string, error) {
return string(buf), nil
}
}
// ConvertMap returns a func to handle converting a map[string]interface{} for
// a driver.
func ConvertMap(u *dburl.URL) func(map[string]interface{}) (string, error) {
if d, ok := drivers[u.Driver]; ok && d.ConvertMap != nil {
return d.ConvertMap
}
return func(v map[string]interface{}) (string, error) {
buf, err := json.Marshal(v)
if err != nil {
return "", err
}
return string(buf), nil
}
}
// ConvertSlice returns a func to handle converting a []interface{} for a
// driver.
func ConvertSlice(u *dburl.URL) func([]interface{}) (string, error) {
if d, ok := drivers[u.Driver]; ok && d.ConvertSlice != nil {
return d.ConvertSlice
}
return func(v []interface{}) (string, error) {
buf, err := json.Marshal(v)
if err != nil {
return "", err
}
return string(buf), nil
}
}
// ConvertDefault returns a func to handle converting a interface{} for a
// driver.
func ConvertDefault(u *dburl.URL) func(interface{}) (string, error) {
if d, ok := drivers[u.Driver]; ok && d.ConvertDefault != nil {
return d.ConvertDefault
}
return func(v interface{}) (string, error) {
return fmt.Sprintf("%v", v), nil
}
}
// BatchAsTransaction returns whether or not a driver requires batched queries
// to be done within a transaction block.
func BatchAsTransaction(u *dburl.URL) bool {
if d, ok := drivers[u.Driver]; ok {
return d.BatchAsTransaction
}
return false
}
// IsBatchQueryPrefix returns whether or not the supplied query prefix is a
// batch query prefix, and the closing prefix. Used to direct the handler to
// continue accumulating statements.
func IsBatchQueryPrefix(u *dburl.URL, prefix string) (string, string, bool) {
// normalize
typ, q := QueryExecType(prefix, "")
d, ok := drivers[u.Driver]
if q || !ok || d.BatchQueryPrefixes == nil {
return typ, "", false
}
end, ok := d.BatchQueryPrefixes[typ]
return typ, end, ok
}
// RowsAffected returns the rows affected for the SQL result for a driver.
func RowsAffected(u *dburl.URL, res sql.Result) (int64, error) {
var count int64
var err error
if d, ok := drivers[u.Driver]; ok && d.RowsAffected != nil {
count, err = d.RowsAffected(res)
} else {
count, err = res.RowsAffected()
}
if err != nil && err.Error() == "no RowsAffected available after DDL statement" {
return 0, nil
}
if err != nil {
return 0, WrapErr(u.Driver, err)
}
return count, nil
}
// Ping pings the database for a driver.
func Ping(ctx context.Context, u *dburl.URL, db *sql.DB) error {
return WrapErr(u.Driver, db.PingContext(ctx))
}
// Lexer returns the syntax lexer for a driver.
func Lexer(u *dburl.URL) chroma.Lexer {
var l chroma.Lexer
if u != nil {
if d, ok := drivers[u.Driver]; ok && d.LexerName != "" {
l = lexers.Get(d.LexerName)
}
}
if l == nil {
l = lexers.Get("sql")
}
l.Config().EnsureNL = false
return l
}
// ForceQueryParameters is a utility func that wraps forcing params of name,
// value pairs.
func ForceQueryParameters(params []string) func(*dburl.URL) {
if len(params)%2 != 0 {
panic("invalid query params")
}
return func(u *dburl.URL) {
if len(params) != 0 {
v := u.Query()
for i := 0; i < len(params); i += 2 {
v.Set(params[i], params[i+1])
}
u.RawQuery = v.Encode()
}
}
}
// NewMetadataReader wraps creating a new database introspector for a driver.
func NewMetadataReader(ctx context.Context, u *dburl.URL, db DB, w io.Writer, opts ...metadata.ReaderOption) (metadata.Reader, error) {
d, ok := drivers[u.Driver]
if !ok || d.NewMetadataReader == nil {
return nil, fmt.Errorf(text.NotSupportedByDriver, `describe commands`, u.Driver)
}
return d.NewMetadataReader(db, opts...), nil
}
// NewMetadataWriter wraps creating a new database metadata printer for a driver.
func NewMetadataWriter(ctx context.Context, u *dburl.URL, db DB, w io.Writer, opts ...metadata.ReaderOption) (metadata.Writer, error) {
d, ok := drivers[u.Driver]
if !ok {
return nil, fmt.Errorf(text.NotSupportedByDriver, `describe commands`, u.Driver)
}
if d.NewMetadataWriter != nil {
return d.NewMetadataWriter(db, w, opts...), nil
}
if d.NewMetadataReader == nil {
return nil, fmt.Errorf(text.NotSupportedByDriver, `describe commands`, u.Driver)
}
newMetadataWriter := metadata.NewDefaultWriter(d.NewMetadataReader(db, opts...))
return newMetadataWriter(db, w), nil
}
// NewCompleter creates a metadata completer for a driver and database
// connection.
func NewCompleter(ctx context.Context, u *dburl.URL, db DB, readerOpts []metadata.ReaderOption, opts ...completer.Option) readline.AutoCompleter {
d, ok := drivers[u.Driver]
if !ok {
return nil
}
if d.NewCompleter != nil {
return d.NewCompleter(db, opts...)
}
if d.NewMetadataReader == nil {
return nil
}
// prepend to allow to override default options
readerOpts = append([]metadata.ReaderOption{
// this needs to be relatively low, since autocomplete is very interactive
metadata.WithTimeout(3 * time.Second),
metadata.WithLimit(1000),
}, readerOpts...)
opts = append([]completer.Option{
completer.WithReader(d.NewMetadataReader(db, readerOpts...)),
completer.WithDB(db),
}, opts...)
return completer.NewDefaultCompleter(opts...)
}
// Copy copies the result set to the destination sql.DB.
func Copy(ctx context.Context, u *dburl.URL, stdout, stderr func() io.Writer, rows *sql.Rows, table string) (int64, error) {
d, ok := drivers[u.Driver]
if !ok {
return 0, WrapErr(u.Driver, text.ErrDriverNotAvailable)
}
if d.Copy == nil {
return 0, fmt.Errorf(text.NotSupportedByDriver, "copy", u.Driver)
}
db, err := Open(ctx, u, stdout, stderr)
if err != nil {
return 0, err
}
defer db.Close()
return d.Copy(ctx, db, rows, table)
}
// CopyWithInsert builds a typical copy handler based on insert.
func CopyWithInsert(placeholder func(int) string) func(ctx context.Context, db *sql.DB, rows *sql.Rows, table string) (int64, error) {
if placeholder == nil {
placeholder = func(n int) string { return fmt.Sprintf("$%d", n) }
}
return func(ctx context.Context, db *sql.DB, rows *sql.Rows, table string) (int64, error) {
return FlexibleCopyWithInsert(ctx, db, rows, table, placeholder, true)
}
}
func FlexibleCopyWithInsert(ctx context.Context, db *sql.DB, rows *sql.Rows, table string, placeholder func(int) string, withTransaction bool) (int64, error) {
columns, err := rows.Columns()
if err != nil {
return 0, fmt.Errorf("failed to fetch source rows columns: %w", err)
}
clen := len(columns)
query := table
if !strings.HasPrefix(strings.ToLower(query), "insert into") {
leftParen := strings.IndexRune(table, '(')
if leftParen == -1 {
colRows, err := db.QueryContext(ctx, "SELECT * FROM "+table+" WHERE 1=0")
if err != nil {
return 0, fmt.Errorf("failed to execute query to determine target table columns: %w", err)
}
columns, err := colRows.Columns()
_ = colRows.Close()
if err != nil {
return 0, fmt.Errorf("failed to fetch target table columns: %w", err)
}
table += "(" + strings.Join(columns, ", ") + ")"
}
// TODO if the db supports multiple rows per insert, create batches of 100 rows
placeholders := make([]string, clen)
for i := 0; i < clen; i++ {
placeholders[i] = placeholder(i + 1)
}
query = "INSERT INTO " + table + " VALUES (" + strings.Join(placeholders, ", ") + ")"
}
var stmt *sql.Stmt
var tx *sql.Tx
if withTransaction {
tx, err = db.BeginTx(ctx, nil)
if err != nil {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
stmt, err = tx.PrepareContext(ctx, query)
} else {
stmt, err = db.PrepareContext(ctx, query)
}
if err != nil {
return 0, fmt.Errorf("failed to prepare insert query: %w", err)
}
defer stmt.Close()
columnTypes, err := rows.ColumnTypes()
if err != nil {
return 0, fmt.Errorf("failed to fetch source column types: %w", err)
}
values := make([]interface{}, clen)
valueRefs := make([]reflect.Value, clen)
actuals := make([]interface{}, clen)
for i := 0; i < len(columnTypes); i++ {
valueRefs[i] = reflect.New(columnTypes[i].ScanType())
values[i] = valueRefs[i].Interface()
}
var n int64
for rows.Next() {
err = rows.Scan(values...)
if err != nil {
return n, fmt.Errorf("failed to scan row: %w", err)
}
//We can't use values... in Exec() below, because some drivers
//don't accept pointer to an argument instead of the arg itself.
for i := range values {
actuals[i] = valueRefs[i].Elem().Interface()
}
res, err := stmt.ExecContext(ctx, actuals...)
if err != nil {
return n, fmt.Errorf("failed to exec insert: %w", err)
}
rn, err := res.RowsAffected()
if err != nil {
return n, fmt.Errorf("failed to check rows affected: %w", err)
}
n += rn
}
// TODO if using batches, flush the last batch,
// TODO prepare another statement and count remaining rows
if tx != nil {
err = tx.Commit()
if err != nil {
return n, fmt.Errorf("failed to commit transaction: %w", err)
}
}
return n, rows.Err()
}
func init() {
dburl.OdbcIgnoreQueryPrefixes = []string{"usql_"}
}
var endRE = regexp.MustCompile(`;?\s*$`)
func StripTrailingSemicolon(_ *dburl.URL, prefix string, sqlstr string) (string, string, bool, error) {
sqlstr = endRE.ReplaceAllString(sqlstr, "")
typ, q := QueryExecType(prefix, sqlstr)
return typ, sqlstr, q, nil
}
================================================
FILE: drivers/drivers_test.go
================================================
// Package drivers_test runs integration tests for drivers package
// on real databases running in containers. During development, to avoid rebuilding
// containers every run, add the `-cleanup=false` flags when calling `go test github.com/xo/usql/drivers`.
package drivers_test
import (
"bytes"
"context"
"database/sql"
"flag"
"fmt"
"log"
"net/url"
"os"
"regexp"
"strings"
"testing"
"time"
dt "github.com/ory/dockertest/v3"
dc "github.com/ory/dockertest/v3/docker"
"github.com/xo/dburl"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
_ "github.com/xo/usql/internal"
)
type Database struct {
BuildArgs []dc.BuildArg
RunOptions *dt.RunOptions
DSN string
ReadyDSN string
Exec []string
DockerPort string
Resource *dt.Resource
URL *dburl.URL
DB *sql.DB
}
const (
pw = "yourStrong123_Password"
)
var (
dbs = map[string]*Database{
"pgsql": {
BuildArgs: []dc.BuildArg{
{Name: "BASE_IMAGE", Value: "postgres:13"},
{Name: "SCHEMA_URL", Value: "https://raw.githubusercontent.com/jOOQ/sakila/main/postgres-sakila-db/postgres-sakila-schema.sql"},
{Name: "TARGET", Value: "/docker-entrypoint-initdb.d"},
{Name: "USER", Value: "root"},
},
RunOptions: &dt.RunOptions{
Name: "usql-pgsql",
Cmd: []string{"-c", "log_statement=all", "-c", "log_min_duration_statement=0"},
Env: []string{"POSTGRES_PASSWORD=pw"},
},
DSN: "postgres://postgres:pw@localhost:%s/postgres?sslmode=disable",
DockerPort: "5432/tcp",
},
"pgx": {
BuildArgs: []dc.BuildArg{
{Name: "BASE_IMAGE", Value: "postgres:13"},
{Name: "SCHEMA_URL", Value: "https://raw.githubusercontent.com/jOOQ/sakila/main/postgres-sakila-db/postgres-sakila-schema.sql"},
{Name: "TARGET", Value: "/docker-entrypoint-initdb.d"},
{Name: "USER", Value: "root"},
},
RunOptions: &dt.RunOptions{
Name: "usql-pgsql",
Cmd: []string{"-c", "log_statement=all", "-c", "log_min_duration_statement=0"},
Env: []string{"POSTGRES_PASSWORD=pw"},
},
DSN: "pgx://postgres:pw@localhost:%s/postgres?sslmode=disable",
DockerPort: "5432/tcp",
},
"mysql": {
BuildArgs: []dc.BuildArg{
{Name: "BASE_IMAGE", Value: "mysql:8"},
{Name: "SCHEMA_URL", Value: "https://raw.githubusercontent.com/jOOQ/sakila/main/mysql-sakila-db/mysql-sakila-schema.sql"},
{Name: "TARGET", Value: "/docker-entrypoint-initdb.d"},
{Name: "USER", Value: "root"},
},
RunOptions: &dt.RunOptions{
Name: "usql-mysql",
Cmd: []string{"--general-log=1", "--general-log-file=/var/lib/mysql/mysql.log"},
Env: []string{"MYSQL_ROOT_PASSWORD=pw"},
},
DSN: "mysql://root:pw@localhost:%s/sakila?parseTime=true",
DockerPort: "3306/tcp",
},
"sqlserver": {
BuildArgs: []dc.BuildArg{
{Name: "BASE_IMAGE", Value: "mcr.microsoft.com/mssql/server:2019-latest"},
{Name: "SCHEMA_URL", Value: "https://raw.githubusercontent.com/jOOQ/sakila/main/sql-server-sakila-db/sql-server-sakila-schema.sql"},
{Name: "TARGET", Value: "/schema"},
{Name: "USER", Value: "mssql:0"},
},
RunOptions: &dt.RunOptions{
Name: "usql-sqlserver",
Env: []string{"ACCEPT_EULA=Y", "SA_PASSWORD=" + pw},
},
DSN: "sqlserver://sa:" + url.QueryEscape(pw) + "@127.0.0.1:%s?database=sakila",
ReadyDSN: "sqlserver://sa:" + url.QueryEscape(pw) + "@127.0.0.1:%s?database=master",
Exec: []string{"/opt/mssql-tools/bin/sqlcmd", "-S", "localhost", "-U", "sa", "-P", pw, "-d", "master", "-i", "/schema/sql-server-sakila-schema.sql"},
DockerPort: "1433/tcp",
},
"trino": {
BuildArgs: []dc.BuildArg{
{Name: "BASE_IMAGE", Value: "trinodb/trino:359"},
},
RunOptions: &dt.RunOptions{
Name: "usql-trino",
},
DSN: "trino://test@localhost:%s/tpch/sf1",
DockerPort: "8080/tcp",
},
"csvq": {
// go test sets working directory to current package regardless of initial working directory
DSN: "csvq://./testdata/csvq",
},
}
cleanup bool
)
func TestMain(m *testing.M) {
var only string
flag.BoolVar(&cleanup, "cleanup", true, "delete containers when finished")
flag.StringVar(&only, "dbs", "", "comma separated list of dbs to test: pgsql, mysql, sqlserver, trino")
flag.Parse()
if only != "" {
runOnly := map[string]struct{}{}
for _, dbName := range strings.Split(only, ",") {
dbName = strings.TrimSpace(dbName)
runOnly[dbName] = struct{}{}
}
for dbName := range dbs {
if _, ok := runOnly[dbName]; !ok {
delete(dbs, dbName)
}
}
}
pool, err := dt.NewPool("")
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
for dbName, db := range dbs {
dsn, hostPort := getConnInfo(dbName, db, pool)
db.URL, err = dburl.Parse(dsn)
if err != nil {
log.Fatalf("Failed to parse %s URL %s: %v", dbName, db.DSN, err)
}
if len(db.Exec) != 0 {
readyDSN := db.ReadyDSN
if db.ReadyDSN == "" {
readyDSN = db.DSN
}
if hostPort != "" {
readyDSN = fmt.Sprintf(db.ReadyDSN, hostPort)
}
readyURL, err := dburl.Parse(readyDSN)
if err != nil {
log.Fatalf("Failed to parse %s ready URL %s: %v", dbName, db.ReadyDSN, err)
}
if err := pool.Retry(func() error {
readyDB, err := drivers.Open(context.Background(), readyURL, nil, nil)
if err != nil {
return err
}
return readyDB.Ping()
}); err != nil {
log.Fatalf("Timed out waiting for %s to be ready: %s", dbName, err)
}
// No TTY attached to facilitate debugging with delve
exitCode, err := db.Resource.Exec(db.Exec, dt.ExecOptions{})
if err != nil || exitCode != 0 {
log.Fatalf("Could not load schema for %s: %s", dbName, err)
}
}
// exponential backoff-retry, because the application in the container might not be ready to accept connections yet
var openErr error
if retryErr := pool.Retry(func() error {
db.DB, openErr = drivers.Open(context.Background(), db.URL, nil, nil)
if openErr != nil {
return openErr
}
return db.DB.Ping()
}); retryErr != nil {
log.Fatalf("Timed out waiting for %s:\n%s\n%s", dbName, retryErr, openErr)
}
}
code := m.Run()
// You can't defer this because os.Exit doesn't care for defer
if cleanup {
for _, db := range dbs {
if db.Resource != nil {
if err := pool.Purge(db.Resource); err != nil {
log.Fatal("Could not purge resource: ", err)
}
}
}
}
os.Exit(code)
}
func getConnInfo(dbName string, db *Database, pool *dt.Pool) (string, string) {
if db.RunOptions == nil {
return db.DSN, ""
}
var ok bool
db.Resource, ok = pool.ContainerByName(db.RunOptions.Name)
if ok && !db.Resource.Container.State.Running {
err := db.Resource.Close()
if err != nil {
log.Fatalf("Failed to clean up stale container %s: %s", dbName, err)
}
ok = false
}
if !ok {
buildOpts := &dt.BuildOptions{
ContextDir: "./testdata/docker",
BuildArgs: db.BuildArgs,
}
var err error
db.Resource, err = pool.BuildAndRunWithBuildOptions(buildOpts, db.RunOptions)
if err != nil {
log.Fatalf("Failed to start %s: %s", dbName, err)
}
}
hostPort := db.Resource.GetPort(db.DockerPort)
return fmt.Sprintf(db.DSN, hostPort), hostPort
}
func TestWriter(t *testing.T) {
type testFunc struct {
label string
f func(w metadata.Writer, u *dburl.URL) error
ignore string
}
testCases := []struct {
dbName string
funcs []testFunc
}{
{
dbName: "pgsql",
funcs: []testFunc{
{
label: "descTable",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.DescribeTableDetails(u, "film*", true, false)
},
},
{
label: "listTables",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.ListTables(u, "tvmsE", "film*", true, false)
},
},
{
label: "listFuncs",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.DescribeFunctions(u, "", "", false, false)
},
},
{
label: "listIndexes",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.ListIndexes(u, "", true, false)
},
},
{
label: "listSchemas",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.ListSchemas(u, "", true, false)
},
},
},
},
{
dbName: "mysql",
funcs: []testFunc{
{
label: "descTable",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.DescribeTableDetails(u, "film*", true, false)
},
},
{
label: "listTables",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.ListTables(u, "tvmsE", "film*", true, false)
},
},
{
label: "listFuncs",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.DescribeFunctions(u, "", "", false, false)
},
},
{
label: "listIndexes",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.ListIndexes(u, "", true, false)
},
},
{
label: "listSchemas",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.ListSchemas(u, "", true, false)
},
},
},
},
{
dbName: "sqlserver",
funcs: []testFunc{
{
label: "descTable",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.DescribeTableDetails(u, "film*", true, false)
},
// primary key indices get random names; ignore them
ignore: "PK__.*__.{16}",
},
{
label: "listTables",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.ListTables(u, "tvmsE", "film*", true, false)
},
},
{
label: "listFuncs",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.DescribeFunctions(u, "", "", false, false)
},
},
{
label: "listIndexes",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.ListIndexes(u, "", true, false)
},
// primary key indices get random names; ignore them
ignore: "PK__.*__.{16}",
},
{
label: "listSchemas",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.ListSchemas(u, "", true, false)
},
},
},
},
{
dbName: "trino",
funcs: []testFunc{
{
label: "descTable",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.DescribeTableDetails(u, "order*", true, false)
},
},
{
label: "listTables",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.ListTables(u, "tvmsE", "order*", true, false)
},
},
{
label: "listSchemas",
f: func(w metadata.Writer, u *dburl.URL) error {
return w.ListSchemas(u, "", true, false)
},
},
},
},
}
for _, test := range testCases {
for _, testFunc := range test.funcs {
actual := fmt.Sprintf("testdata/%s.%s.actual.txt", test.dbName, testFunc.label)
fo, err := os.Create(actual)
if err != nil {
t.Fatalf("Cannot create results file %s: %v", actual, err)
}
db, ok := dbs[test.dbName]
if !ok {
continue
}
w, err := drivers.NewMetadataWriter(context.Background(), db.URL, db.DB, fo)
if err != nil {
log.Fatalf("Could not create writer %s %s: %v", test.dbName, testFunc.label, err)
}
err = testFunc.f(w, db.URL)
if err != nil {
log.Fatalf("Could not write %s %s: %v", test.dbName, testFunc.label, err)
}
err = fo.Close()
if err != nil {
t.Fatalf("Cannot close results file %s: %v", actual, err)
}
expected := fmt.Sprintf("testdata/%s.%s.expected.txt", test.dbName, testFunc.label)
err = filesEqual(expected, actual, testFunc.ignore)
if err != nil {
t.Error(err)
}
}
}
}
func TestCopy(t *testing.T) {
pg, ok := dbs["pgsql"]
if !ok {
t.Skip("Skipping copy tests, as they require PostgreSQL which was not selected for tests")
}
// setup test data, ignoring errors, since there'll be duplicates
_, _ = pg.DB.Exec("ALTER TABLE staff DROP CONSTRAINT staff_address_id_fkey")
_, _ = pg.DB.Exec("ALTER TABLE staff DROP CONSTRAINT staff_store_id_fkey")
_, _ = pg.DB.Exec("INSERT INTO staff VALUES (1, 'John', 'Doe', 1, 'john@invalid.com', 1, true, 'jdoe', 'abc', now(), 'abcd')")
type setupQuery struct {
query string
check bool
}
testCases := []struct {
dbName string
setupQueries []setupQuery
src string
dest string
}{
{
dbName: "pgsql",
setupQueries: []setupQuery{
{query: "DROP TABLE staff_copy"},
{query: "CREATE TABLE staff_copy AS SELECT * FROM staff WHERE 0=1", check: true},
},
src: "select * from staff",
dest: "staff_copy",
},
{
dbName: "pgsql",
setupQueries: []setupQuery{
{query: "DROP TABLE staff_copy"},
{query: "CREATE TABLE staff_copy AS SELECT * FROM staff WHERE 0=1", check: true},
},
src: "select * from staff",
dest: "public.staff_copy",
},
{
dbName: "pgx",
setupQueries: []setupQuery{
{query: "DROP TABLE staff_copy"},
{query: "CREATE TABLE staff_copy AS SELECT * FROM staff WHERE 0=1", check: true},
},
src: "select * from staff",
dest: "staff_copy",
},
{
dbName: "pgx",
setupQueries: []setupQuery{
{query: "DROP TABLE staff_copy"},
{query: "CREATE TABLE staff_copy AS SELECT * FROM staff WHERE 0=1", check: true},
},
src: "select * from staff",
dest: "public.staff_copy",
},
{
dbName: "mysql",
setupQueries: []setupQuery{
{query: "DROP TABLE staff_copy"},
{query: "CREATE TABLE staff_copy AS SELECT * FROM staff WHERE 0=1", check: true},
},
src: "select staff_id, first_name, last_name, address_id, picture, email, store_id, active, username, password, last_update from staff",
dest: "staff_copy(staff_id, first_name, last_name, address_id, picture, email, store_id, active, username, password, last_update)",
},
{
dbName: "sqlserver",
setupQueries: []setupQuery{
{query: "DROP TABLE staff_copy"},
{query: "SELECT * INTO staff_copy FROM staff WHERE 0=1", check: true},
},
src: "select first_name, last_name, address_id, picture, email, store_id, active, username, password, last_update from staff",
dest: "staff_copy(first_name, last_name, address_id, picture, email, store_id, active, username, password, last_update)",
},
{
dbName: "csvq",
setupQueries: []setupQuery{
{query: "CREATE TABLE IF NOT EXISTS staff_copy AS SELECT * FROM `staff.csv` WHERE 0=1", check: true},
},
src: "select first_name, last_name, address_id, email, store_id, active, username, password, last_update from staff",
dest: "staff_copy",
},
}
for _, test := range testCases {
db, ok := dbs[test.dbName]
if !ok {
continue
}
t.Run(test.dbName, func(t *testing.T) {
// TODO test copy from a different DB, maybe csvq?
// TODO test copy from same DB
for _, q := range test.setupQueries {
_, err := db.DB.Exec(q.query)
if q.check && err != nil {
t.Fatalf("Failed to run setup query `%s`: %v", q.query, err)
}
}
rows, err := pg.DB.Query(test.src)
if err != nil {
t.Fatalf("Could not get rows to copy: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var rlen int64 = 1
n, err := drivers.Copy(ctx, db.URL, nil, nil, rows, test.dest)
if err != nil {
t.Fatalf("Could not copy: %v", err)
}
if n != rlen {
t.Fatalf("Expected to copy %d rows but got %d", rlen, n)
}
})
}
}
// filesEqual compares the files at paths a and b and returns an error if
// the content is not equal. Ignore is a regex. All matches will be removed
// from the file contents before comparison.
func filesEqual(a, b, ignore string) error {
// per comment, better to not read an entire file into memory
// this is simply a trivial example.
f1, err := os.ReadFile(a)
if err != nil {
return fmt.Errorf("Cannot read file %s: %w", a, err)
}
f2, err := os.ReadFile(b)
if err != nil {
return fmt.Errorf("Cannot read file %s: %w", b, err)
}
if ignore != "" {
reg, err := regexp.Compile(ignore)
if err != nil {
return fmt.Errorf("Cannot compile regex (%s): %w", ignore, err)
}
f1 = reg.ReplaceAllLiteral(f1, []byte{})
f2 = reg.ReplaceAllLiteral(f2, []byte{})
}
if !bytes.Equal(f1, f2) {
return fmt.Errorf("Files %s and %s have different contents", a, b)
}
return nil
}
================================================
FILE: drivers/duckdb/duckdb.go
================================================
// Package duckdb defines and registers usql's DuckDB driver. Requires CGO.
//
// See: https://github.com/duckdb/duckdb-go
package duckdb
import (
"context"
"database/sql"
"fmt"
"io"
"strings"
_ "github.com/duckdb/duckdb-go/v2" // DRIVER
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
infos "github.com/xo/usql/drivers/metadata/informationschema"
mymeta "github.com/xo/usql/drivers/metadata/mysql"
)
type metaReader struct {
metadata.LoggingReader
}
var (
_ metadata.CatalogReader = &metaReader{}
_ metadata.ColumnStatReader = &metaReader{}
)
func (r metaReader) Catalogs(metadata.Filter) (*metadata.CatalogSet, error) {
qstr := `SHOW catalogs`
rows, closeRows, err := r.Query(qstr)
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.Catalog{}
for rows.Next() {
rec := metadata.Catalog{}
err = rows.Scan(&rec.Catalog)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewCatalogSet(results), nil
}
func (r metaReader) ColumnStats(f metadata.Filter) (*metadata.ColumnStatSet, error) {
names := []string{}
if f.Catalog != "" {
names = append(names, f.Catalog+".")
}
if f.Schema != "" {
names = append(names, f.Schema+".")
}
names = append(names, f.Parent)
rows, closeRows, err := r.Query(fmt.Sprintf("SHOW STATS FOR %s", strings.Join(names, "")))
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.ColumnStat{}
for rows.Next() {
rec := metadata.ColumnStat{Catalog: f.Catalog, Schema: f.Schema, Table: f.Parent}
name := sql.NullString{}
avgWidth := sql.NullInt32{}
numDistinct := sql.NullInt64{}
nullFrac := sql.NullFloat64{}
numRows := sql.NullInt64{}
min := sql.NullString{}
max := sql.NullString{}
err = rows.Scan(
&name,
&avgWidth,
&numDistinct,
&nullFrac,
&numRows,
&min,
&max,
)
if err != nil {
return nil, err
}
if !name.Valid {
continue
}
rec.Name = name.String
if avgWidth.Valid {
rec.AvgWidth = int(avgWidth.Int32)
}
if numDistinct.Valid {
rec.NumDistinct = numDistinct.Int64
}
if nullFrac.Valid {
rec.NullFrac = nullFrac.Float64
}
if min.Valid {
rec.Min = min.String
}
if max.Valid {
rec.Max = max.String
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewColumnStatSet(results), nil
}
func init() {
newReader := func(db drivers.DB, opts ...metadata.ReaderOption) metadata.Reader {
ir := infos.New(
infos.WithPlaceholder(func(int) string { return "?" }),
infos.WithCustomClauses(map[infos.ClauseName]string{
infos.ColumnsColumnSize: "0",
infos.ColumnsNumericScale: "0",
infos.ColumnsNumericPrecRadix: "0",
infos.ColumnsCharOctetLength: "0",
}),
infos.WithFunctions(false),
infos.WithSequences(false),
infos.WithIndexes(false),
infos.WithConstraints(false),
infos.WithColumnPrivileges(false),
infos.WithUsagePrivileges(false),
)(db, opts...)
mr := &metaReader{
LoggingReader: metadata.NewLoggingReader(db, opts...),
}
return metadata.NewPluginReader(ir, mr)
}
drivers.Register("duckdb", drivers.Driver{
AllowMultilineComments: true,
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
err := db.QueryRowContext(ctx, `SELECT library_version FROM pragma_version()`).Scan(&ver)
if err != nil {
return "", err
}
return "DuckDB " + ver, nil
},
NewMetadataReader: newReader,
NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer {
return metadata.NewDefaultWriter(newReader(db, opts...))(db, w)
},
Copy: drivers.CopyWithInsert(func(int) string { return "?" }),
NewCompleter: mymeta.NewCompleter,
})
}
================================================
FILE: drivers/dynamodb/dynamodb.go
================================================
// Package dynamodb defines and registers usql's DynamoDb driver.
//
// See: https://github.com/btnguyen2k/godynamo
package dynamodb
import (
_ "github.com/btnguyen2k/godynamo" // DRIVER
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("godynamo", drivers.Driver{})
}
================================================
FILE: drivers/errors.go
================================================
package drivers
import (
"strings"
"unicode"
)
// Error is a wrapper to standardize errors.
type Error struct {
Driver string
Err error
}
// WrapErr wraps an error using the specified driver when err is not nil.
func WrapErr(driver string, err error) error {
if err == nil {
return nil
}
// avoid double wrapping error
if _, ok := err.(*Error); ok {
return err
}
return &Error{driver, err}
}
// Error satisfies the error interface, returning simple information about the
// wrapped error in standardized way.
func (e *Error) Error() string {
if d, ok := drivers[e.Driver]; ok {
n := e.Driver
if d.Name != "" {
n = d.Name
}
s := n
var msg string
if d.Err != nil {
var code string
code, msg = d.Err(e.Err)
if code != "" {
s += ": " + code
}
} else {
msg = e.Err.Error()
}
return s + ": " + chop(msg, n)
}
return e.Driver + ": " + chop(e.Err.Error(), e.Driver)
}
// Unwrap returns the original error.
func (e *Error) Unwrap() error {
return e.Err
}
// chop chops off a "prefix: " prefix from a string.
func chop(s, prefix string) string {
return strings.TrimLeftFunc(strings.TrimPrefix(strings.TrimSpace(s), prefix+":"), unicode.IsSpace)
}
================================================
FILE: drivers/exasol/exasol.go
================================================
// Package exasol defines and registers usql's Exasol driver.
//
// See: https://github.com/exasol/exasol-driver-go
package exasol
import (
"context"
"regexp"
_ "github.com/exasol/exasol-driver-go" // DRIVER
"github.com/xo/usql/drivers"
)
func init() {
errCodeRE := regexp.MustCompile(`^\[([0-9]+)]\s+`)
drivers.Register("exasol", drivers.Driver{
AllowMultilineComments: true,
LowerColumnNames: true,
Copy: drivers.CopyWithInsert(func(int) string { return "?" }),
Err: func(err error) (string, string) {
code, msg := "", err.Error()
if m := errCodeRE.FindStringSubmatch(msg); m != nil {
code, msg = m[1], errCodeRE.ReplaceAllString(msg, "")
}
return code, msg
},
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
if err := db.QueryRowContext(ctx, `SELECT param_value FROM exa_metadata WHERE param_name = 'databaseProductVersion'`).Scan(&ver); err != nil {
return "", err
}
return "Exasol " + ver, nil
},
})
}
================================================
FILE: drivers/firebird/firebird.go
================================================
// Package firebird defines and registers usql's Firebird driver.
//
// See: https://github.com/nakagami/firebirdsql
package firebird
import (
"context"
_ "github.com/nakagami/firebirdsql" // DRIVER: firebirdsql
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("firebirdsql", drivers.Driver{
AllowMultilineComments: true,
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
err := db.QueryRowContext(
ctx,
`SELECT rdb$get_context('SYSTEM', 'ENGINE_VERSION') FROM rdb$database;`,
).Scan(&ver)
if err != nil {
return "", err
}
return "Firebird " + ver, nil
},
})
}
================================================
FILE: drivers/flightsql/flightsql.go
================================================
// Package flightsql defines and registers usql's FlightSQL driver.
//
// See: https://github.com/apache/arrow/tree/main/go/arrow/flight/flightsql/driver
package flightsql
import (
_ "github.com/apache/arrow/go/v17/arrow/flight/flightsql/driver" // DRIVER
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("flightsql", drivers.Driver{})
}
================================================
FILE: drivers/godror/godror.go
================================================
// Package godror defines and registers usql's GO DRiver for ORacle driver.
// Requires CGO. Uses Oracle's ODPI-C (instant client) library.
//
// See: https://github.com/godror/godror
// Group: all
package godror
import (
"errors"
"fmt"
"strings"
_ "github.com/godror/godror" // DRIVER
"github.com/xo/usql/drivers/oracle/orshared"
)
func init() {
orshared.Register(
"godror",
// unwrap error
func(err error) (string, string) {
if e := errors.Unwrap(err); e != nil {
err = e
}
code, msg := "", err.Error()
if e, ok := err.(interface {
Code() int
}); ok {
code = fmt.Sprintf("ORA-%05d", e.Code())
}
if e, ok := err.(interface {
Message() string
}); ok {
msg = e.Message()
}
if i := strings.LastIndex(msg, "ORA-"); msg == "" && i != -1 {
msg = msg[i:]
if j := strings.Index(msg, ":"); j != -1 {
msg = msg[j+1:]
if code == "" {
code = msg[i:j]
}
}
}
return code, strings.TrimSpace(msg)
},
// is password error
func(err error) bool {
if e := errors.Unwrap(err); e != nil {
err = e
}
if e, ok := err.(interface {
Code() int
}); ok {
return e.Code() == 1017 || e.Code() == 1005
}
return false
},
)
}
================================================
FILE: drivers/h2/h2.go
================================================
// Package h2 defines and registers usql's Apache H2 driver.
//
// See: https://github.com/jmrobles/h2go
package h2
import (
_ "github.com/jmrobles/h2go" // DRIVER
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("h2", drivers.Driver{
AllowDollar: true,
AllowMultilineComments: true,
AllowCComments: true,
})
}
================================================
FILE: drivers/hive/hive.go
================================================
// Package hive defines and registers usql's Apache Hive driver.
//
// See: https://github.com/sql-machine-learning/gohive
package hive
import (
"github.com/xo/dburl"
"github.com/xo/usql/drivers"
_ "sqlflow.org/gohive" // DRIVER
)
func init() {
drivers.Register("hive", drivers.Driver{
ForceParams: func(u *dburl.URL) {
if u.User != nil && u.Query().Get("auth") == "" {
drivers.ForceQueryParameters([]string{"auth", "PLAIN"})(u)
}
},
})
}
================================================
FILE: drivers/ignite/ignite.go
================================================
// Package ignite defines and registers usql's Apache Ignite driver.
//
// See: https://github.com/amsokol/ignite-go-client
package ignite
import (
"strconv"
"github.com/amsokol/ignite-go-client/binary/errors"
_ "github.com/amsokol/ignite-go-client/sql" // DRIVER
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("ignite", drivers.Driver{
Err: func(err error) (string, string) {
if e, ok := err.(*errors.IgniteError); ok {
return strconv.Itoa(int(e.IgniteStatus)), e.IgniteMessage
}
return "", err.Error()
},
})
}
================================================
FILE: drivers/impala/impala.go
================================================
// Package impala defines and registers usql's Apache Impala driver.
//
// See: https://github.com/sclgo/impala-go
package impala
import (
"context"
"database/sql"
"errors"
"github.com/sclgo/impala-go" // DRIVER
"github.com/xo/usql/drivers"
meta "github.com/xo/usql/drivers/metadata/impala"
)
func init() {
drivers.Register("impala", drivers.Driver{
NewMetadataReader: meta.New,
Copy: func(ctx context.Context, db *sql.DB, rows *sql.Rows, table string) (int64, error) {
placeholder := func(int) string {
return "?"
}
return drivers.FlexibleCopyWithInsert(ctx, db, rows, table, placeholder, false)
},
IsPasswordErr: func(err error) bool {
var authError *impala.AuthError
return errors.As(err, &authError)
},
})
}
================================================
FILE: drivers/maxcompute/maxcompute.go
================================================
// Package maxcompute defines and registers usql's Alibaba MaxCompute driver.
//
// See: https://github.com/sql-machine-learning/gomaxcompute
package maxcompute
import (
"github.com/xo/usql/drivers"
_ "sqlflow.org/gomaxcompute" // DRIVER
)
func init() {
drivers.Register("maxcompute", drivers.Driver{})
}
================================================
FILE: drivers/metadata/impala/metadata.go
================================================
package impala
import (
"context"
"database/sql"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
driver "github.com/sclgo/impala-go"
)
type MetaReader struct {
meta *driver.Metadata
}
func (r MetaReader) Columns(filter metadata.Filter) (*metadata.ColumnSet, error) {
columnIds, err := r.meta.GetColumns(context.Background(), filter.Schema, filter.Parent, filter.Name)
if err != nil {
return nil, err
}
columns := make([]metadata.Column, len(columnIds))
for i, columnId := range columnIds {
columns[i] = metadata.Column{
Schema: columnId.Schema,
Table: columnId.TableName,
Name: columnId.ColumnName,
}
}
return metadata.NewColumnSet(columns), nil
}
func (r MetaReader) Schemas(filter metadata.Filter) (*metadata.SchemaSet, error) {
schemaNames, err := r.meta.GetSchemas(context.Background(), filter.Name)
if err != nil {
return nil, err
}
schemas := make([]metadata.Schema, len(schemaNames))
for i, name := range schemaNames {
schemas[i] = metadata.Schema{
Schema: name,
}
}
return metadata.NewSchemaSet(schemas), nil
}
func (r MetaReader) Tables(filter metadata.Filter) (*metadata.TableSet, error) {
tableIds, err := r.meta.GetTables(context.Background(), filter.Schema, filter.Name)
if err != nil {
return nil, err
}
tables := make([]metadata.Table, len(tableIds))
for i, table := range tableIds {
tables[i] = metadata.Table{
Schema: table.Schema,
Name: table.Name,
Type: table.Type,
}
}
return metadata.NewTableSet(tables), nil
}
var (
_ metadata.SchemaReader = (*MetaReader)(nil)
_ metadata.TableReader = (*MetaReader)(nil)
_ metadata.ColumnReader = (*MetaReader)(nil)
)
func New(db drivers.DB, _ ...metadata.ReaderOption) metadata.Reader {
if sqlDb, ok := db.(*sql.DB); ok {
return &MetaReader{
meta: driver.NewMetadata(sqlDb),
}
} else {
return struct{}{} // reader with no capabilities
}
}
================================================
FILE: drivers/metadata/informationschema/metadata.go
================================================
// Package informationschema provides metadata readers that query tables from
// the information_schema schema. It tries to be database agnostic,
// but there is a set of options to configure what tables and columns to expect.
package informationschema
import (
"database/sql"
"fmt"
"strings"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
"github.com/xo/usql/text"
)
// InformationSchema metadata reader
type InformationSchema struct {
metadata.LoggingReader
pf func(int) string
hasFunctions bool
hasSequences bool
hasIndexes bool
hasConstraints bool
hasCheckConstraints bool
hasTablePrivileges bool
hasColumnPrivileges bool
hasUsagePrivileges bool
clauses map[ClauseName]string
limit int
systemSchemas []string
currentSchema string
dataTypeFormatter func(metadata.Column) string
}
var _ metadata.BasicReader = &InformationSchema{}
type Logger interface {
Println(...interface{})
}
type ClauseName string
const (
ColumnsDataType = ClauseName("columns.data_type")
ColumnsColumnSize = ClauseName("columns.column_size")
ColumnsNumericScale = ClauseName("columns.numeric_scale")
ColumnsNumericPrecRadix = ClauseName("columns.numeric_precision_radix")
ColumnsCharOctetLength = ClauseName("columns.character_octet_length")
FunctionColumnsColumnSize = ClauseName("function_columns.column_size")
FunctionColumnsNumericScale = ClauseName("function_columns.numeric_scale")
FunctionColumnsNumericPrecRadix = ClauseName("function_columns.numeric_precision_radix")
FunctionColumnsCharOctetLength = ClauseName("function_columns.character_octet_length")
FunctionsSecurityType = ClauseName("functions.security_type")
ConstraintIsDeferrable = ClauseName("constraint_columns.is_deferrable")
ConstraintInitiallyDeferred = ClauseName("constraint_columns.initially_deferred")
ConstraintJoinCond = ClauseName("constraint_join.fk")
SequenceColumnsIncrement = ClauseName("sequence_columns.increment")
PrivilegesGrantor = ClauseName("privileges.grantor")
)
// New InformationSchema reader
func New(opts ...metadata.ReaderOption) func(drivers.DB, ...metadata.ReaderOption) metadata.Reader {
s := &InformationSchema{
pf: func(n int) string { return fmt.Sprintf("$%d", n) },
hasFunctions: true,
hasSequences: true,
hasIndexes: true,
hasConstraints: true,
hasCheckConstraints: true,
hasTablePrivileges: true,
hasColumnPrivileges: true,
hasUsagePrivileges: true,
clauses: map[ClauseName]string{
ColumnsDataType: "data_type",
ColumnsColumnSize: "COALESCE(character_maximum_length, numeric_precision, datetime_precision, 0)",
ColumnsNumericScale: "COALESCE(numeric_scale, 0)",
ColumnsNumericPrecRadix: "COALESCE(numeric_precision_radix, 10)",
ColumnsCharOctetLength: "COALESCE(character_octet_length, 0)",
FunctionColumnsColumnSize: "COALESCE(character_maximum_length, numeric_precision, datetime_precision, 0)",
FunctionColumnsNumericScale: "COALESCE(numeric_scale, 0)",
FunctionColumnsNumericPrecRadix: "COALESCE(numeric_precision_radix, 10)",
FunctionColumnsCharOctetLength: "COALESCE(character_octet_length, 0)",
FunctionsSecurityType: "security_type",
ConstraintIsDeferrable: "t.is_deferrable",
ConstraintInitiallyDeferred: "t.initially_deferred",
SequenceColumnsIncrement: "increment",
PrivilegesGrantor: "grantor",
},
systemSchemas: []string{"information_schema"},
dataTypeFormatter: func(col metadata.Column) string { return col.DataType },
}
// apply InformationSchema specific options
for _, o := range opts {
o(s)
}
return func(db drivers.DB, opts ...metadata.ReaderOption) metadata.Reader {
s.LoggingReader = metadata.NewLoggingReader(db, opts...)
return s
}
}
// WithPlaceholder generator function, that usually returns either `?` or `$n`,
// where `n` is the argument.
func WithPlaceholder(pf func(int) string) metadata.ReaderOption {
return func(r metadata.Reader) {
r.(*InformationSchema).pf = pf
}
}
// WithCustomClauses to use different expressions for some columns
func WithCustomClauses(cols map[ClauseName]string) metadata.ReaderOption {
return func(r metadata.Reader) {
for k, v := range cols {
r.(*InformationSchema).clauses[k] = v
}
}
}
// WithFunctions when the `routines` and `parameters` tables exists
func WithFunctions(fun bool) metadata.ReaderOption {
return func(r metadata.Reader) {
r.(*InformationSchema).hasFunctions = fun
}
}
// WithIndexes when the `statistics` table exists
func WithIndexes(ind bool) metadata.ReaderOption {
return func(r metadata.Reader) {
r.(*InformationSchema).hasIndexes = ind
}
}
// WithConstraints when the `statistics` table exists
func WithConstraints(con bool) metadata.ReaderOption {
return func(r metadata.Reader) {
r.(*InformationSchema).hasConstraints = con
}
}
// WithCheckConstraints when the `statistics` table exists
func WithCheckConstraints(con bool) metadata.ReaderOption {
return func(r metadata.Reader) {
r.(*InformationSchema).hasCheckConstraints = con
}
}
// WithSequences when the `sequences` table exists
func WithSequences(seq bool) metadata.ReaderOption {
return func(r metadata.Reader) {
r.(*InformationSchema).hasSequences = seq
}
}
// WithTablePrivileges when the `table_privileges` table exists
func WithTablePrivileges(t bool) metadata.ReaderOption {
return func(r metadata.Reader) {
r.(*InformationSchema).hasTablePrivileges = t
}
}
// WithColumnPrivileges when the `column_privileges` table exists
func WithColumnPrivileges(c bool) metadata.ReaderOption {
return func(r metadata.Reader) {
r.(*InformationSchema).hasColumnPrivileges = c
}
}
// WithUsagePrivileges when the `usage_privileges` table exists
func WithUsagePrivileges(u bool) metadata.ReaderOption {
return func(r metadata.Reader) {
r.(*InformationSchema).hasUsagePrivileges = u
}
}
// WithSystemSchemas that are ignored unless WithSystem filter is true
func WithSystemSchemas(schemas []string) metadata.ReaderOption {
return func(r metadata.Reader) {
r.(*InformationSchema).systemSchemas = schemas
}
}
// WithCurrentSchema expression to filter by when OnlyVisible filter is true
func WithCurrentSchema(expr string) metadata.ReaderOption {
return func(r metadata.Reader) {
r.(*InformationSchema).currentSchema = expr
}
}
// WithDataTypeFormatter function to build updated string representation of data type
// from Column
func WithDataTypeFormatter(f func(metadata.Column) string) metadata.ReaderOption {
return func(r metadata.Reader) {
r.(*InformationSchema).dataTypeFormatter = f
}
}
func (s *InformationSchema) SetLimit(l int) {
s.limit = l
}
// Columns from selected catalog (or all, if empty), matching schemas and tables
func (s InformationSchema) Columns(f metadata.Filter) (*metadata.ColumnSet, error) {
columns := []string{
"table_catalog",
"table_schema",
"table_name",
"column_name",
"ordinal_position",
s.clauses[ColumnsDataType],
"COALESCE(column_default, '')",
"COALESCE(is_nullable, '') AS is_nullable",
s.clauses[ColumnsColumnSize],
s.clauses[ColumnsNumericScale],
s.clauses[ColumnsNumericPrecRadix],
s.clauses[ColumnsCharOctetLength],
}
qstr := "SELECT\n " + strings.Join(columns, ",\n ") + " FROM information_schema.columns\n"
conds, vals := s.conditions(1, f, formats{
catalog: "table_catalog LIKE %s",
schema: "table_schema LIKE %s",
notSchemas: "table_schema NOT IN (%s)",
parent: "table_name LIKE %s",
})
rows, closeRows, err := s.query(qstr, conds, "table_catalog, table_schema, table_name, ordinal_position", vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewColumnSet([]metadata.Column{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.Column{}
for rows.Next() {
rec := metadata.Column{}
err = rows.Scan(
&rec.Catalog,
&rec.Schema,
&rec.Table,
&rec.Name,
&rec.OrdinalPosition,
&rec.DataType,
&rec.Default,
&rec.IsNullable,
&rec.ColumnSize,
&rec.DecimalDigits,
&rec.NumPrecRadix,
&rec.CharOctetLength,
)
if err != nil {
return nil, err
}
rec.DataType = s.dataTypeFormatter(rec)
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewColumnSet(results), nil
}
// Tables from selected catalog (or all, if empty), matching schemas, names and types
func (s InformationSchema) Tables(f metadata.Filter) (*metadata.TableSet, error) {
qstr := `SELECT
table_catalog,
table_schema,
table_name,
table_type
FROM information_schema.tables
`
conds, vals := s.conditions(1, f, formats{
catalog: "table_catalog LIKE %s",
schema: "table_schema LIKE %s",
notSchemas: "table_schema NOT IN (%s)",
name: "table_name LIKE %s",
types: "table_type IN (%s)",
})
if len(conds) != 0 {
qstr += " WHERE " + strings.Join(conds, " AND ")
}
addSequences := false
for _, t := range f.Types {
if t == "SEQUENCE" && s.hasSequences {
addSequences = true
}
}
if addSequences {
qstr += `
UNION ALL
SELECT
sequence_catalog AS table_catalog,
sequence_schema AS table_schema,
sequence_name AS table_name,
'SEQUENCE' AS table_type
FROM information_schema.sequences
`
conds, seqVals := s.conditions(len(vals)+1, f, formats{
catalog: "sequence_catalog LIKE %s",
schema: "sequence_schema LIKE %s",
notSchemas: "sequence_schema NOT IN (%s)",
name: "sequence_name LIKE %s",
})
vals = append(vals, seqVals...)
if len(conds) != 0 {
qstr += " WHERE " + strings.Join(conds, " AND ")
}
}
rows, closeRows, err := s.query(qstr, []string{}, "table_catalog, table_schema, table_type, table_name", vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewTableSet([]metadata.Table{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.Table{}
for rows.Next() {
rec := metadata.Table{}
err = rows.Scan(&rec.Catalog, &rec.Schema, &rec.Name, &rec.Type)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewTableSet(results), nil
}
// Schemas from selected catalog (or all, if empty), matching schemas and tables
func (s InformationSchema) Schemas(f metadata.Filter) (*metadata.SchemaSet, error) {
qstr := `SELECT
schema_name,
catalog_name
FROM information_schema.schemata
`
conds, vals := s.conditions(1, f, formats{
catalog: "catalog_name LIKE %s",
name: "schema_name LIKE %s",
notSchemas: "schema_name NOT IN (%s)",
})
rows, closeRows, err := s.query(qstr, conds, "catalog_name, schema_name", vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewSchemaSet([]metadata.Schema{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.Schema{}
for rows.Next() {
rec := metadata.Schema{}
err = rows.Scan(&rec.Schema, &rec.Catalog)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewSchemaSet(results), nil
}
// Functions from selected catalog (or all, if empty), matching schemas, names and types
func (s InformationSchema) Functions(f metadata.Filter) (*metadata.FunctionSet, error) {
if !s.hasFunctions {
return nil, text.ErrNotSupported
}
columns := []string{
"specific_name",
"routine_catalog",
"routine_schema",
"routine_name",
"COALESCE(routine_type, '')",
"COALESCE(data_type, '')",
"routine_definition",
"COALESCE(external_language, routine_body) AS language",
"is_deterministic",
s.clauses[FunctionsSecurityType],
}
qstr := "SELECT\n " + strings.Join(columns, ",\n ") + " FROM information_schema.routines\n"
conds, vals := s.conditions(1, f, formats{
catalog: "routine_catalog LIKE %s",
schema: "routine_schema LIKE %s",
notSchemas: "routine_schema NOT IN (%s)",
name: "routine_name LIKE %s",
types: "routine_type IN (%s)",
})
rows, closeRows, err := s.query(qstr, conds, "routine_catalog, routine_schema, routine_name, COALESCE(routine_type, '')", vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewFunctionSet([]metadata.Function{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.Function{}
for rows.Next() {
rec := metadata.Function{}
err = rows.Scan(
&rec.SpecificName,
&rec.Catalog,
&rec.Schema,
&rec.Name,
&rec.Type,
&rec.ResultType,
&rec.Source,
&rec.Language,
&rec.Volatility,
&rec.Security,
)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewFunctionSet(results), nil
}
// FunctionColumns (arguments) from selected catalog (or all, if empty), matching schemas and functions
func (s InformationSchema) FunctionColumns(f metadata.Filter) (*metadata.FunctionColumnSet, error) {
if !s.hasFunctions {
return nil, text.ErrNotSupported
}
columns := []string{
"specific_catalog",
"specific_schema",
"specific_name",
"COALESCE(parameter_name, '')",
"ordinal_position",
"COALESCE(parameter_mode, '')",
"COALESCE(data_type, '')",
s.clauses[FunctionColumnsColumnSize],
s.clauses[FunctionColumnsNumericScale],
s.clauses[FunctionColumnsNumericPrecRadix],
s.clauses[FunctionColumnsCharOctetLength],
}
qstr := "SELECT\n " + strings.Join(columns, ",\n ") + " FROM information_schema.parameters\n"
conds, vals := s.conditions(1, f, formats{
catalog: "specific_catalog LIKE %s",
schema: "specific_schema LIKE %s",
notSchemas: "specific_schema NOT IN (%s)",
parent: "specific_name LIKE %s",
})
rows, closeRows, err := s.query(qstr, conds, "specific_catalog, specific_schema, specific_name, ordinal_position, COALESCE(parameter_name, '')", vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewFunctionColumnSet([]metadata.FunctionColumn{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.FunctionColumn{}
for rows.Next() {
rec := metadata.FunctionColumn{}
err = rows.Scan(
&rec.Catalog,
&rec.Schema,
&rec.FunctionName,
&rec.Name,
&rec.OrdinalPosition,
&rec.Type,
&rec.DataType,
&rec.ColumnSize,
&rec.DecimalDigits,
&rec.NumPrecRadix,
&rec.CharOctetLength,
)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewFunctionColumnSet(results), nil
}
// Indexes from selected catalog (or all, if empty), matching schemas and names
func (s InformationSchema) Indexes(f metadata.Filter) (*metadata.IndexSet, error) {
if !s.hasIndexes {
return nil, text.ErrNotSupported
}
qstr := `SELECT
table_catalog,
index_schema,
table_name,
index_name,
CASE WHEN non_unique = 0 THEN 'YES' ELSE 'NO' END AS is_unique,
CASE WHEN index_name = 'PRIMARY' THEN 'YES' ELSE 'NO' END AS is_primary,
index_type
FROM information_schema.statistics
`
conds, vals := s.conditions(1, f, formats{
catalog: "table_catalog LIKE %s",
schema: "index_schema LIKE %s",
notSchemas: "index_schema NOT IN (%s)",
parent: "table_name LIKE %s",
name: "index_name LIKE %s",
})
if len(conds) != 0 {
qstr += " WHERE " + strings.Join(conds, " AND ")
}
qstr += `
GROUP BY table_catalog, index_schema, table_name, index_name,
CASE WHEN non_unique = 0 THEN 'YES' ELSE 'NO' END,
CASE WHEN index_name = 'PRIMARY' THEN 'YES' ELSE 'NO' END,
index_type`
rows, closeRows, err := s.query(qstr, []string{}, "table_catalog, index_schema, table_name, index_name", vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewIndexSet([]metadata.Index{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.Index{}
for rows.Next() {
rec := metadata.Index{}
err = rows.Scan(&rec.Catalog, &rec.Schema, &rec.Table, &rec.Name, &rec.IsUnique, &rec.IsPrimary, &rec.Type)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewIndexSet(results), nil
}
// IndexColumns from selected catalog (or all, if empty), matching schemas and indexes
func (s InformationSchema) IndexColumns(f metadata.Filter) (*metadata.IndexColumnSet, error) {
if !s.hasIndexes {
return nil, text.ErrNotSupported
}
qstr := `SELECT
i.table_catalog,
i.table_schema,
i.table_name,
i.index_name,
i.column_name,
c.data_type,
i.seq_in_index
FROM information_schema.statistics i
JOIN information_schema.columns c ON
i.table_catalog = c.table_catalog AND
i.table_schema = c.table_schema AND
i.table_name = c.table_name AND
i.column_name = c.column_name
`
conds, vals := s.conditions(1, f, formats{
catalog: "i.table_catalog LIKE %s",
schema: "index_schema LIKE %s",
notSchemas: "index_schema NOT IN (%s)",
parent: "i.table_name LIKE %s",
name: "index_name LIKE %s",
})
rows, closeRows, err := s.query(qstr, conds, "i.table_catalog, index_schema, table_name, index_name, seq_in_index", vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewIndexColumnSet([]metadata.IndexColumn{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.IndexColumn{}
for rows.Next() {
rec := metadata.IndexColumn{}
err = rows.Scan(&rec.Catalog, &rec.Schema, &rec.Table, &rec.IndexName, &rec.Name, &rec.DataType, &rec.OrdinalPosition)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewIndexColumnSet(results), nil
}
// Constraints from selected catalog (or all, if empty), matching schemas and names
func (s InformationSchema) Constraints(f metadata.Filter) (*metadata.ConstraintSet, error) {
if !s.hasConstraints {
return nil, text.ErrNotSupported
}
columns := []string{
"t.constraint_catalog",
"t.table_schema",
"t.table_name",
"t.constraint_name",
"t.constraint_type",
s.clauses[ConstraintIsDeferrable],
s.clauses[ConstraintInitiallyDeferred],
"COALESCE(r.unique_constraint_catalog, '') AS foreign_catalog",
"COALESCE(r.unique_constraint_schema, '') AS foreign_schema",
"COALESCE(f.table_name, '') AS foreign_table",
"COALESCE(r.unique_constraint_name, '') AS foreign_constraint",
"COALESCE(r.match_option, '') AS match_options",
"COALESCE(r.update_rule, '') AS update_rule",
"COALESCE(r.delete_rule, '') AS delete_rule",
"COALESCE(c.check_clause, '') AS check_clause",
}
qstr := "SELECT\n " + strings.Join(columns, ",\n ") + `
FROM information_schema.table_constraints t
LEFT JOIN information_schema.referential_constraints r ON t.constraint_catalog = r.constraint_catalog
AND t.constraint_schema = r.constraint_schema
AND t.constraint_name = r.constraint_name
AND t.constraint_type = 'FOREIGN KEY'
LEFT JOIN information_schema.table_constraints f ON r.unique_constraint_catalog = f.constraint_catalog
AND r.unique_constraint_schema = f.constraint_schema
AND r.unique_constraint_name = f.constraint_name
` + s.clauses[ConstraintJoinCond] + `
LEFT JOIN information_schema.check_constraints c ON t.constraint_catalog = c.constraint_catalog
AND t.constraint_schema = c.constraint_schema
AND t.constraint_name = c.constraint_name
`
conds, vals := s.conditions(1, f, formats{
catalog: "t.constraint_catalog LIKE %s",
schema: "t.table_schema LIKE %s",
notSchemas: "t.table_schema NOT IN (%s)",
parent: "t.table_name LIKE %s",
reference: "f.table_name LIKE %s",
name: "t.constraint_name LIKE %s",
})
if len(conds) != 0 {
qstr += " WHERE " + strings.Join(conds, " AND ")
}
rows, closeRows, err := s.query(qstr, []string{}, "t.constraint_catalog, t.table_schema, t.table_name, t.constraint_name", vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewConstraintSet([]metadata.Constraint{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.Constraint{}
for rows.Next() {
rec := metadata.Constraint{}
err = rows.Scan(
&rec.Catalog,
&rec.Schema,
&rec.Table,
&rec.Name,
&rec.Type,
&rec.IsDeferrable,
&rec.IsInitiallyDeferred,
&rec.ForeignCatalog,
&rec.ForeignSchema,
&rec.ForeignTable,
&rec.ForeignName,
&rec.MatchType,
&rec.UpdateRule,
&rec.DeleteRule,
&rec.CheckClause,
)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewConstraintSet(results), nil
}
// ConstraintColumns from selected catalog (or all, if empty), matching schemas and constraints
func (s InformationSchema) ConstraintColumns(f metadata.Filter) (*metadata.ConstraintColumnSet, error) {
if !s.hasConstraints {
return nil, text.ErrNotSupported
}
vals := []interface{}{}
qstr := ""
if s.hasCheckConstraints {
qstr = `SELECT
c.constraint_catalog,
c.table_schema,
c.table_name,
c.constraint_name,
c.column_name,
1 AS ordinal_position,
'' AS foreign_catalog,
'' AS foreign_schema,
'' AS foreign_table,
'' AS foreign_name
FROM information_schema.constraint_column_usage c
`
conds, checkVals := s.conditions(len(vals)+1, f, formats{
catalog: "c.constraint_catalog LIKE %s",
schema: "c.table_schema LIKE %s",
notSchemas: "c.table_schema NOT IN (%s)",
parent: "c.table_name LIKE %s",
name: "c.constraint_name LIKE %s",
})
if len(conds) != 0 {
qstr += " WHERE " + strings.Join(conds, " AND ")
vals = append(vals, checkVals...)
}
qstr += `
UNION ALL
`
}
qstr += `SELECT
c.constraint_catalog,
c.table_schema,
c.table_name,
c.constraint_name,
c.column_name,
c.ordinal_position,
COALESCE(f.constraint_catalog, '') AS foreign_catalog,
COALESCE(f.table_schema, '') AS foreign_schema,
COALESCE(f.table_name, '') AS foreign_table,
COALESCE(f.column_name, '') AS foreign_name
FROM information_schema.key_column_usage c
LEFT JOIN information_schema.referential_constraints r ON c.constraint_catalog = r.constraint_catalog
AND c.constraint_schema = r.constraint_schema
AND c.constraint_name = r.constraint_name
LEFT JOIN information_schema.key_column_usage f ON r.unique_constraint_catalog = f.constraint_catalog
AND r.unique_constraint_schema = f.constraint_schema
AND r.unique_constraint_name = f.constraint_name
` + s.clauses[ConstraintJoinCond] + `
AND c.position_in_unique_constraint = f.ordinal_position
`
conds, keyVals := s.conditions(len(vals)+1, f, formats{
catalog: "c.constraint_catalog LIKE %s",
schema: "c.table_schema LIKE %s",
notSchemas: "c.table_schema NOT IN (%s)",
parent: "c.table_name LIKE %s",
reference: "f.table_name LIKE %s",
name: "c.constraint_name LIKE %s",
})
if len(conds) != 0 {
qstr += " WHERE " + strings.Join(conds, " AND ")
vals = append(vals, keyVals...)
}
rows, closeRows, err := s.query(qstr, []string{}, "constraint_catalog, table_schema, table_name, constraint_name, ordinal_position, column_name", vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewConstraintColumnSet([]metadata.ConstraintColumn{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.ConstraintColumn{}
i := 1
for rows.Next() {
rec := metadata.ConstraintColumn{OrdinalPosition: i}
i++
err = rows.Scan(
&rec.Catalog,
&rec.Schema,
&rec.Table,
&rec.Constraint,
&rec.Name,
&rec.OrdinalPosition,
&rec.ForeignCatalog,
&rec.ForeignSchema,
&rec.ForeignTable,
&rec.ForeignName,
)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewConstraintColumnSet(results), nil
}
// Sequences from selected catalog (or all, if empty), matching schemas and names
func (s InformationSchema) Sequences(f metadata.Filter) (*metadata.SequenceSet, error) {
if !s.hasSequences {
return nil, text.ErrNotSupported
}
columns := []string{
"sequence_catalog",
"sequence_schema",
"sequence_name",
"data_type",
"start_value",
"minimum_value",
"maximum_value",
s.clauses[SequenceColumnsIncrement],
"cycle_option",
}
qstr := "SELECT\n " + strings.Join(columns, ",\n ") + " FROM information_schema.sequences\n"
conds, vals := s.conditions(1, f, formats{
catalog: "sequence_catalog LIKE %s",
schema: "sequence_schema LIKE %s",
notSchemas: "sequence_schema NOT IN (%s)",
name: "sequence_name LIKE %s",
})
rows, closeRows, err := s.query(qstr, conds, "sequence_catalog, sequence_schema, sequence_name", vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewSequenceSet([]metadata.Sequence{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.Sequence{}
for rows.Next() {
rec := metadata.Sequence{}
err = rows.Scan(&rec.Catalog, &rec.Schema, &rec.Name, &rec.DataType, &rec.Start, &rec.Min, &rec.Max, &rec.Increment, &rec.Cycles)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewSequenceSet(results), nil
}
// PrivilegeSummaries of privileges on tables, views and sequences from selected catalog (or all, if empty), matching schemas and names
func (s InformationSchema) PrivilegeSummaries(f metadata.Filter) (*metadata.PrivilegeSummarySet, error) {
if !s.hasTablePrivileges && !s.hasColumnPrivileges && !s.hasUsagePrivileges {
return nil, text.ErrNotSupported
}
qstrs := []string{}
conds, vals := s.conditions(1, f, formats{
catalog: "object_catalog LIKE %s",
schema: "object_schema LIKE %s",
notSchemas: "object_schema NOT IN (%s)",
name: "object_name LIKE %s",
types: "object_type IN (%s)",
})
if s.hasTablePrivileges {
columns := []string{
"t.table_catalog AS object_catalog",
"t.table_schema AS object_schema",
"t.table_name AS object_name",
"t.table_type AS object_type",
"'' AS column_name",
"COALESCE(grantee, '') AS grantee",
"COALESCE(" + s.clauses[PrivilegesGrantor] + ", '') AS grantor",
"COALESCE(privilege_type, '') AS privilege_type",
"CASE WHEN is_grantable='YES' THEN 1 ELSE 0 END AS is_grantable",
}
// `tables` is on the left side of the join to also list tables that have no privileges set.
qstr := "SELECT\n" +
" " + strings.Join(columns, ", ") + "\n" +
"FROM information_schema.tables t\n" +
"LEFT JOIN information_schema.table_privileges tp\n" +
" ON t.table_catalog = tp.table_catalog AND t.table_schema = tp.table_schema AND t.table_name = tp.table_name"
qstrs = append(qstrs, qstr)
}
if s.hasColumnPrivileges {
columns := []string{
"t.table_catalog AS object_catalog",
"t.table_schema AS object_schema",
"t.table_name AS object_name",
"t.table_type AS object_type",
"column_name",
"grantee",
s.clauses[PrivilegesGrantor] + " AS grantor",
"privilege_type",
"CASE WHEN is_grantable='YES' THEN 1 ELSE 0 END AS is_grantable",
}
qstr := "SELECT\n" +
" " + strings.Join(columns, ", ") + "\n" +
"FROM information_schema.column_privileges cp\n" +
"LEFT JOIN information_schema.tables t\n" +
" ON t.table_catalog = cp.table_catalog AND t.table_schema = cp.table_schema AND t.table_name = cp.table_name"
qstrs = append(qstrs, qstr)
}
if s.hasUsagePrivileges {
columns := []string{
"object_catalog",
"object_schema",
"object_name",
"object_type",
"'' AS column_name",
"grantee",
s.clauses[PrivilegesGrantor] + " AS grantor",
"privilege_type",
"CASE WHEN is_grantable='YES' THEN 1 ELSE 0 END AS is_grantable",
}
qstr := "SELECT\n" +
" " + strings.Join(columns, ", ") + "\n" +
"FROM information_schema.usage_privileges"
qstrs = append(qstrs, qstr)
}
// In the query result, table and column level privileges will be on separate rows.
// Each table or column can have multiple privileges (i.e rows).
// For table level privileges the `column_name` column is empty.
qstr := "SELECT * FROM (\n" + strings.Join(qstrs, "\nUNION ALL\n") + "\n) AS subquery"
rows, closeRows, err := s.query(
qstr,
conds,
"object_catalog, object_schema, object_type, object_name, column_name, grantee, grantor, privilege_type",
vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewPrivilegeSummarySet([]metadata.PrivilegeSummary{}), nil
}
return nil, err
}
defer closeRows()
type row struct {
Catalog string
Schema string
Name string
ObjectType string
Column string
Grantee string
Grantor string
PrivilegeType string
IsGrantable bool
}
// The rows need to be aggregated into one `metadata.PrivilegeSummary` object per table. The rows are ordered by table such that we can append
// to the current `metadata.PrivilegeSummary` as long as we are processing the same table.
results := []metadata.PrivilegeSummary{}
curSummary := &metadata.PrivilegeSummary{}
for rows.Next() {
r := row{}
err = rows.Scan(&r.Catalog, &r.Schema, &r.Name, &r.ObjectType, &r.Column, &r.Grantee, &r.Grantor, &r.PrivilegeType, &r.IsGrantable)
if err != nil {
return nil, err
}
if curSummary.Catalog != r.Catalog || curSummary.Schema != r.Schema || curSummary.Name != r.Name {
summary := metadata.PrivilegeSummary{
Catalog: r.Catalog,
Schema: r.Schema,
Name: r.Name,
ObjectType: r.ObjectType,
ObjectPrivileges: metadata.ObjectPrivileges{},
ColumnPrivileges: metadata.ColumnPrivileges{},
}
results = append(results, summary)
curSummary = &results[len(results)-1]
}
switch {
// If the row specifies neither column nor table level privileges
case r.PrivilegeType == "":
// If row specifies table level privilege
case r.Column == "":
objPrivilege := metadata.ObjectPrivilege{Grantee: r.Grantee, Grantor: r.Grantor, PrivilegeType: r.PrivilegeType, IsGrantable: r.IsGrantable}
curSummary.ObjectPrivileges = append(curSummary.ObjectPrivileges, objPrivilege)
// If row specifies column level privilege
default:
colPrivilege := metadata.ColumnPrivilege{Column: r.Column, Grantee: r.Grantee, Grantor: r.Grantor, PrivilegeType: r.PrivilegeType, IsGrantable: r.IsGrantable}
curSummary.ColumnPrivileges = append(curSummary.ColumnPrivileges, colPrivilege)
}
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewPrivilegeSummarySet(results), nil
}
func (s InformationSchema) conditions(baseParam int, filter metadata.Filter, formats formats) ([]string, []interface{}) {
conds := []string{}
vals := []interface{}{}
if filter.Catalog != "" && formats.catalog != "" {
vals = append(vals, filter.Catalog)
conds = append(conds, fmt.Sprintf(formats.catalog, s.pf(baseParam)))
baseParam++
}
if filter.Schema != "" && formats.schema != "" {
vals = append(vals, filter.Schema)
conds = append(conds, fmt.Sprintf(formats.schema, s.pf(baseParam)))
baseParam++
}
if !filter.WithSystem && formats.notSchemas != "" && len(s.systemSchemas) != 0 {
pholders := []string{}
for _, v := range s.systemSchemas {
if v == filter.Schema {
continue
}
vals = append(vals, v)
pholders = append(pholders, s.pf(baseParam))
baseParam++
}
if len(pholders) != 0 {
conds = append(conds, fmt.Sprintf(formats.notSchemas, strings.Join(pholders, ", ")))
}
}
if filter.OnlyVisible && formats.schema != "" && s.currentSchema != "" {
conds = append(conds, fmt.Sprintf(formats.schema, s.currentSchema))
}
if filter.Parent != "" && formats.parent != "" {
vals = append(vals, filter.Parent)
conds = append(conds, fmt.Sprintf(formats.parent, s.pf(baseParam)))
baseParam++
}
if filter.Reference != "" && formats.reference != "" {
vals = append(vals, filter.Reference)
conds = append(conds, fmt.Sprintf(formats.reference, s.pf(baseParam)))
baseParam++
}
if filter.Name != "" && formats.name != "" {
vals = append(vals, filter.Name)
conds = append(conds, fmt.Sprintf(formats.name, s.pf(baseParam)))
baseParam++
}
if len(filter.Types) != 0 && formats.types != "" {
pholders := []string{}
for _, t := range filter.Types {
vals = append(vals, t)
pholders = append(pholders, s.pf(baseParam))
baseParam++
}
if len(pholders) != 0 {
conds = append(conds, fmt.Sprintf(formats.types, strings.Join(pholders, ", ")))
}
}
return conds, vals
}
type formats struct {
catalog string
schema string
notSchemas string
parent string
reference string
name string
types string
}
func (s InformationSchema) query(qstr string, conds []string, order string, vals ...interface{}) (*sql.Rows, func(), error) {
if len(conds) != 0 {
qstr += "\nWHERE " + strings.Join(conds, " AND ")
}
if order != "" {
qstr += "\nORDER BY " + order
}
if s.limit != 0 {
qstr += fmt.Sprintf("\nLIMIT %d", s.limit)
}
return s.Query(qstr, vals...)
}
================================================
FILE: drivers/metadata/informationschema/metadata_test.go
================================================
// Package informationschema_test runs integration tests for informationschema package
// on real databases running in containers. During development, to avoid rebuilding
// containers every run, add the `-cleanup=false` flags when calling `go test`.
package informationschema_test
import (
"database/sql"
"flag"
"fmt"
"log"
"net/url"
"os"
"sort"
"strings"
"testing"
_ "github.com/go-sql-driver/mysql"
"github.com/google/go-cmp/cmp"
_ "github.com/microsoft/go-mssqldb" // DRIVER: sqlserver
dt "github.com/ory/dockertest/v3"
dc "github.com/ory/dockertest/v3/docker"
_ "github.com/trinodb/trino-go-client/trino"
"github.com/xo/usql/drivers/metadata"
infos "github.com/xo/usql/drivers/metadata/informationschema"
_ "github.com/xo/usql/drivers/postgres"
)
type Database struct {
BuildArgs []dc.BuildArg
RunOptions *dt.RunOptions
Exec []string
Driver string
URL string
ReadinessURL string
DockerPort string
Resource *dt.Resource
DB *sql.DB
Opts []metadata.ReaderOption
Reader metadata.BasicReader
}
const (
pw = "yourStrong123_Password"
)
var (
dbs = map[string]*Database{
"pgsql": {
BuildArgs: []dc.BuildArg{
{Name: "BASE_IMAGE", Value: "postgres:13"},
{Name: "SCHEMA_URL", Value: "https://raw.githubusercontent.com/jOOQ/sakila/main/postgres-sakila-db/postgres-sakila-schema.sql"},
{Name: "TARGET", Value: "/docker-entrypoint-initdb.d"},
{Name: "USER", Value: "root"},
},
RunOptions: &dt.RunOptions{
Name: "usql-pgsql",
Cmd: []string{"-c", "log_statement=all", "-c", "log_min_duration_statement=0"},
Env: []string{"POSTGRES_PASSWORD=pw"},
},
Driver: "postgres",
URL: "postgres://postgres:pw@localhost:%s/postgres?sslmode=disable",
DockerPort: "5432/tcp",
Opts: []metadata.ReaderOption{
infos.WithIndexes(false),
infos.WithCustomClauses(map[infos.ClauseName]string{
infos.ColumnsColumnSize: "COALESCE(character_maximum_length, numeric_precision, datetime_precision, interval_precision, 0)",
infos.FunctionColumnsColumnSize: "COALESCE(character_maximum_length, numeric_precision, datetime_precision, interval_precision, 0)",
}),
infos.WithSystemSchemas([]string{"pg_catalog", "pg_toast", "information_schema"}),
},
},
"mysql": {
BuildArgs: []dc.BuildArg{
{Name: "BASE_IMAGE", Value: "mysql:8"},
{Name: "SCHEMA_URL", Value: "https://raw.githubusercontent.com/jOOQ/sakila/main/mysql-sakila-db/mysql-sakila-schema.sql"},
{Name: "TARGET", Value: "/docker-entrypoint-initdb.d"},
{Name: "USER", Value: "root"},
},
RunOptions: &dt.RunOptions{
Name: "usql-mysql",
Cmd: []string{"--general-log=1", "--general-log-file=/var/lib/mysql/mysql.log"},
Env: []string{"MYSQL_ROOT_PASSWORD=pw"},
},
Driver: "mysql",
URL: "root:pw@(localhost:%s)/mysql?parseTime=true",
DockerPort: "3306/tcp",
Opts: []metadata.ReaderOption{
infos.WithPlaceholder(func(int) string { return "?" }),
infos.WithCheckConstraints(false),
infos.WithCustomClauses(map[infos.ClauseName]string{
infos.ColumnsDataType: "column_type",
infos.ColumnsNumericPrecRadix: "10",
infos.FunctionColumnsNumericPrecRadix: "10",
infos.ConstraintIsDeferrable: "''",
infos.ConstraintInitiallyDeferred: "''",
infos.PrivilegesGrantor: "''",
infos.ConstraintJoinCond: "AND r.referenced_table_name = f.table_name",
}),
infos.WithSystemSchemas([]string{"mysql", "information_schema", "performance_schema", "sys"}),
infos.WithCurrentSchema("COALESCE(DATABASE(), '%')"),
infos.WithUsagePrivileges(false),
infos.WithSequences(false),
},
},
"sqlserver": {
BuildArgs: []dc.BuildArg{
{Name: "BASE_IMAGE", Value: "mcr.microsoft.com/mssql/server:2019-latest"},
{Name: "SCHEMA_URL", Value: "https://raw.githubusercontent.com/jOOQ/sakila/main/sql-server-sakila-db/sql-server-sakila-schema.sql"},
{Name: "TARGET", Value: "/schema"},
{Name: "USER", Value: "mssql:0"},
},
RunOptions: &dt.RunOptions{
Name: "usql-sqlserver",
Env: []string{"ACCEPT_EULA=Y", "SA_PASSWORD=" + pw},
},
Exec: []string{"/opt/mssql-tools/bin/sqlcmd", "-S", "localhost", "-U", "sa", "-P", pw, "-d", "master", "-i", "/schema/sql-server-sakila-schema.sql"},
Driver: "sqlserver",
URL: "sqlserver://sa:" + url.QueryEscape(pw) + "@127.0.0.1:%s?database=sakila",
ReadinessURL: "sqlserver://sa:" + url.QueryEscape(pw) + "@127.0.0.1:%s",
DockerPort: "1433/tcp",
Opts: []metadata.ReaderOption{
infos.WithPlaceholder(func(n int) string { return fmt.Sprintf("@p%d", n) }),
infos.WithIndexes(false),
infos.WithConstraints(false),
infos.WithCustomClauses(map[infos.ClauseName]string{
infos.FunctionsSecurityType: "''",
}),
infos.WithSystemSchemas([]string{
"db_accessadmin",
"db_backupoperator",
"db_datareader",
"db_datawriter",
"db_ddladmin",
"db_denydatareader",
"db_denydatawriter",
"db_owner",
"db_securityadmin",
"INFORMATION_SCHEMA",
"sys",
}),
infos.WithUsagePrivileges(false),
infos.WithSequences(false),
},
},
"trino": {
BuildArgs: []dc.BuildArg{
{Name: "BASE_IMAGE", Value: "trinodb/trino:351"},
},
RunOptions: &dt.RunOptions{
Name: "usql-trino",
},
Driver: "trino",
URL: "http://test@localhost:%s?catalog=tpch&schema=sf1",
DockerPort: "8080/tcp",
Opts: []metadata.ReaderOption{
infos.WithPlaceholder(func(int) string { return "?" }),
infos.WithIndexes(false),
infos.WithConstraints(false),
infos.WithCustomClauses(map[infos.ClauseName]string{
infos.ColumnsColumnSize: "0",
infos.ColumnsNumericScale: "0",
infos.ColumnsNumericPrecRadix: "0",
infos.ColumnsCharOctetLength: "0",
infos.FunctionColumnsColumnSize: "0",
infos.FunctionColumnsNumericScale: "0",
infos.FunctionColumnsNumericPrecRadix: "0",
infos.FunctionColumnsCharOctetLength: "0",
}),
},
},
}
cleanup bool
)
func TestMain(m *testing.M) {
var only string
flag.BoolVar(&cleanup, "cleanup", true, "delete containers when finished")
flag.StringVar(&only, "dbs", "", "comma separated list of dbs to test: pgsql, mysql, sqlserver, trino")
flag.Parse()
if only != "" {
runOnly := map[string]struct{}{}
for _, dbName := range strings.Split(only, ",") {
dbName = strings.TrimSpace(dbName)
runOnly[dbName] = struct{}{}
}
for dbName := range dbs {
if _, ok := runOnly[dbName]; !ok {
delete(dbs, dbName)
}
}
}
pool, err := dt.NewPool("")
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
for dbName, db := range dbs {
var ok bool
db.Resource, ok = pool.ContainerByName(db.RunOptions.Name)
if !ok {
buildOpts := &dt.BuildOptions{
ContextDir: "../../testdata/docker",
BuildArgs: db.BuildArgs,
}
db.Resource, err = pool.BuildAndRunWithBuildOptions(buildOpts, db.RunOptions)
if err != nil {
log.Fatal("Could not start resource: ", err)
}
}
state := db.Resource.Container.State.Status
if state != "created" && state != "running" {
log.Fatalf("Unexpected container state for %s: %s", dbName, state)
}
url := db.URL
if db.ReadinessURL != "" {
url = db.ReadinessURL
}
port := db.Resource.GetPort(db.DockerPort)
if db.DB, err = waitForDbConnection(db.Driver, pool, url, port); err != nil {
log.Fatalf("Timed out waiting for %s: %s", dbName, err)
}
if len(db.Exec) != 0 {
exitCode, err := db.Resource.Exec(db.Exec, dt.ExecOptions{
StdIn: os.Stdin,
StdOut: os.Stdout,
StdErr: os.Stderr,
TTY: true,
})
if err != nil || exitCode != 0 {
log.Fatal("Could not load schema: ", err)
}
}
// Reconnect with actual URL if a separate URL for readiness checking was used
if db.ReadinessURL != "" {
if db.DB, err = waitForDbConnection(db.Driver, pool, db.URL, port); err != nil {
log.Fatalf("Timed out waiting for %s: %s", dbName, err)
}
}
db.Reader = infos.New(db.Opts...)(db.DB).(metadata.BasicReader)
}
code := m.Run()
// You can't defer this because os.Exit doesn't care for defer
if cleanup {
for _, db := range dbs {
if err := pool.Purge(db.Resource); err != nil {
log.Fatal("Could not purge resource: ", err)
}
}
}
os.Exit(code)
}
func waitForDbConnection(driver string, pool *dt.Pool, url string, port string) (*sql.DB, error) {
// exponential backoff-retry, because the application in the container might not be ready to accept connections yet
var db *sql.DB
if err := pool.Retry(func() error {
var err error
db, err = sql.Open(driver, fmt.Sprintf(url, port))
if err != nil {
return err
}
return db.Ping()
}); err != nil {
return nil, err
}
return db, nil
}
func TestSchemas(t *testing.T) {
expected := map[string]string{
"pgsql": "information_schema, pg_catalog, pg_toast, public",
"mysql": "information_schema, mysql, performance_schema, sakila, sys",
"sqlserver": "db_accessadmin, db_backupoperator, db_datareader, db_datawriter, db_ddladmin, db_denydatareader, db_denydatawriter, db_owner, db_securityadmin, dbo, guest, INFORMATION_SCHEMA, sys",
"trino": "information_schema, sf1, sf100, sf1000, sf10000, sf100000, sf300, sf3000, sf30000, tiny",
}
for dbName, db := range dbs {
r := db.Reader
result, err := r.Schemas(metadata.Filter{WithSystem: true})
if err != nil {
log.Fatalf("Could not read %s schemas: %v", dbName, err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Schema)
}
actual := strings.Join(names, ", ")
if actual != expected[dbName] {
t.Errorf("Wrong %s schema names, expected:\n %v\ngot:\n %v", dbName, expected[dbName], names)
}
}
}
func TestTables(t *testing.T) {
schemas := map[string]string{
"pgsql": "public",
"mysql": "sakila",
"sqlserver": "dbo",
"trino": "sf1",
}
expected := map[string]string{
"pgsql": "actor, address, category, city, country, customer, film, film_actor, film_category, inventory, language, payment, payment_p2007_01, payment_p2007_02, payment_p2007_03, payment_p2007_04, payment_p2007_05, payment_p2007_06, rental, staff, store, actor_info, customer_list, film_list, nicer_but_slower_film_list, sales_by_film_category, sales_by_store, staff_list",
"mysql": "actor, address, category, city, country, customer, film, film_actor, film_category, film_text, inventory, language, payment, rental, staff, store, actor_info, customer_list, film_list, nicer_but_slower_film_list, sales_by_film_category, sales_by_store, staff_list",
"sqlserver": "actor, address, category, city, country, customer, film, film_actor, film_category, film_text, inventory, language, payment, rental, staff, store, customer_list, film_list, sales_by_film_category, sales_by_store, staff_list",
"trino": "customer, lineitem, nation, orders, part, partsupp, region, supplier",
}
for dbName, db := range dbs {
r := db.Reader
result, err := r.Tables(metadata.Filter{Schema: schemas[dbName], Types: []string{"BASE TABLE", "TABLE", "VIEW"}})
if err != nil {
log.Fatalf("Could not read %s tables: %v", dbName, err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Name)
}
actual := strings.Join(names, ", ")
if actual != expected[dbName] {
t.Errorf("Wrong %s table names, expected:\n %v\ngot:\n %v", dbName, expected[dbName], names)
}
}
}
func TestColumns(t *testing.T) {
schemas := map[string]string{
"pgsql": "public",
"mysql": "sakila",
"sqlserver": "dbo",
"trino": "sf1",
}
tables := map[string]string{
"pgsql": "film%",
"mysql": "film%",
"sqlserver": "film%",
"trino": "orders",
}
expectedColumns := map[string]string{
"pgsql": "film_id, title, description, release_year, language_id, original_language_id, rental_duration, rental_rate, length, replacement_cost, rating, last_update, special_features, fulltext, actor_id, film_id, last_update, film_id, category_id, last_update, fid, title, description, category, price, length, rating, actors",
"mysql": "film_id, title, description, release_year, language_id, original_language_id, rental_duration, rental_rate, length, replacement_cost, rating, special_features, last_update, actor_id, film_id, last_update, film_id, category_id, last_update, FID, title, description, category, price, length, rating, actors, film_id, title, description",
"sqlserver": "film_id, title, description, release_year, language_id, original_language_id, rental_duration, rental_rate, length, replacement_cost, rating, special_features, last_update, actor_id, film_id, last_update, film_id, category_id, last_update, FID, title, description, category, price, length, rating, actors, film_id, title, description",
"trino": "orderkey, custkey, orderstatus, totalprice, orderdate, orderpriority, clerk, shippriority, comment",
}
expectedTypes := map[string]string{
"mysql": "int unsigned, varchar(255), text, year, int unsigned, int unsigned, tinyint unsigned, decimal(4,2), smallint unsigned, decimal(5,2), enum('G','PG','PG-13','R','NC-17'), set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes'), timestamp, int unsigned, int unsigned, timestamp, int unsigned, int unsigned, timestamp, int unsigned, varchar(255), text, varchar(25), decimal(4,2), smallint unsigned, enum('G','PG','PG-13','R','NC-17'), text, int, varchar(255), text",
}
for dbName, db := range dbs {
r := db.Reader
result, err := r.Columns(metadata.Filter{Schema: schemas[dbName], Parent: tables[dbName]})
if err != nil {
log.Fatalf("Could not read %s columns: %v", dbName, err)
}
names := []string{}
types := []string{}
for result.Next() {
names = append(names, result.Get().Name)
types = append(types, result.Get().DataType)
}
actualColumns := strings.Join(names, ", ")
actualTypes := strings.Join(types, ", ")
if expected, ok := expectedColumns[dbName]; ok && actualColumns != expected {
t.Errorf("Wrong %s column names, expected:\n %v, got:\n %v", dbName, expected, names)
}
if expected, ok := expectedTypes[dbName]; ok && actualTypes != expected {
t.Errorf("Wrong %s column types, expected:\n %v, got:\n %v", dbName, expected, types)
}
}
}
func TestFunctions(t *testing.T) {
schemas := map[string]string{
"pgsql": "public",
"mysql": "sakila",
}
expected := map[string]string{
"pgsql": "_group_concat, film_in_stock, film_not_in_stock, get_customer_balance, group_concat, inventory_held_by_customer, inventory_in_stock, last_day, last_updated, rewards_report",
"mysql": "film_in_stock, film_not_in_stock, get_customer_balance, inventory_held_by_customer, inventory_in_stock, rewards_report",
}
for dbName, db := range dbs {
if schemas[dbName] == "" {
continue
}
r := infos.New(db.Opts...)(db.DB).(metadata.FunctionReader)
result, err := r.Functions(metadata.Filter{Schema: schemas[dbName]})
if err != nil {
log.Fatalf("Could not read %s functions: %v", dbName, err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Name)
}
actual := strings.Join(names, ", ")
if actual != expected[dbName] {
t.Errorf("Wrong %s function names, expected:\n %v\ngot:\n %v", dbName, expected[dbName], names)
}
}
}
func TestFunctionColumns(t *testing.T) {
schemas := map[string]string{
"pgsql": "public",
"mysql": "sakila",
}
tables := map[string]string{
"pgsql": "film%",
"mysql": "film%",
}
expected := map[string]string{
"pgsql": "p_film_id, p_store_id, p_film_count, p_film_id, p_store_id, p_film_count",
"mysql": "p_film_id, p_store_id, p_film_count, p_film_id, p_store_id, p_film_count",
}
for dbName, db := range dbs {
if schemas[dbName] == "" {
continue
}
r := infos.New(db.Opts...)(db.DB).(metadata.FunctionColumnReader)
result, err := r.FunctionColumns(metadata.Filter{Schema: schemas[dbName], Parent: tables[dbName]})
if err != nil {
log.Fatalf("Could not read %s function columns: %v", dbName, err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Name)
}
actual := strings.Join(names, ", ")
if actual != expected[dbName] {
t.Errorf("Wrong %s function column names, expected:\n %v, got:\n %v", dbName, expected[dbName], names)
}
}
}
func TestIndexes(t *testing.T) {
schemas := map[string]string{
"mysql": "sakila",
}
expected := map[string]string{
"mysql": "actor.idx_actor_last_name, actor.PRIMARY, address.idx_fk_city_id, address.PRIMARY, category.PRIMARY, city.idx_fk_country_id, city.PRIMARY, country.PRIMARY, customer.idx_fk_address_id, customer.idx_fk_store_id, customer.idx_last_name, customer.PRIMARY, film.idx_fk_language_id, film.idx_fk_original_language_id, film.idx_title, film.PRIMARY, film_actor.idx_fk_film_id, film_actor.PRIMARY, film_category.fk_film_category_category, film_category.PRIMARY, film_text.idx_title_description, film_text.PRIMARY, inventory.idx_fk_film_id, inventory.idx_store_id_film_id, inventory.PRIMARY, language.PRIMARY, payment.fk_payment_rental, payment.idx_fk_customer_id, payment.idx_fk_staff_id, payment.PRIMARY, rental.idx_fk_customer_id, rental.idx_fk_inventory_id, rental.idx_fk_staff_id, rental.PRIMARY, rental.rental_date, staff.idx_fk_address_id, staff.idx_fk_store_id, staff.PRIMARY, store.idx_fk_address_id, store.idx_unique_manager, store.PRIMARY",
}
for dbName, db := range dbs {
if schemas[dbName] == "" {
continue
}
r := infos.New(db.Opts...)(db.DB).(metadata.IndexReader)
result, err := r.Indexes(metadata.Filter{Schema: schemas[dbName]})
if err != nil {
log.Fatalf("Could not read %s indexes: %v", dbName, err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Table+"."+result.Get().Name)
}
actual := strings.Join(names, ", ")
if actual != expected[dbName] {
t.Errorf("Wrong %s index names, expected:\n %v\ngot:\n %v", dbName, expected[dbName], names)
}
}
}
func TestIndexColumns(t *testing.T) {
schemas := map[string]string{
"mysql": "sakila",
}
tables := map[string]string{
"mysql": "idx%",
}
expected := map[string]string{
"mysql": "last_name, city_id, country_id, address_id, store_id, last_name, language_id, original_language_id, title, film_id, title, description, film_id, store_id, film_id, customer_id, staff_id, customer_id, inventory_id, staff_id, address_id, store_id, address_id, manager_staff_id",
}
for dbName, db := range dbs {
if schemas[dbName] == "" {
continue
}
r := infos.New(db.Opts...)(db.DB).(metadata.IndexColumnReader)
result, err := r.IndexColumns(metadata.Filter{Schema: schemas[dbName], Name: tables[dbName]})
if err != nil {
log.Fatalf("Could not read %s index columns: %v", dbName, err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Name)
}
actual := strings.Join(names, ", ")
if actual != expected[dbName] {
t.Errorf("Wrong %s index column names, expected:\n %v, got:\n %v", dbName, expected[dbName], names)
}
}
}
func TestConstraints(t *testing.T) {
schemas := map[string]string{
"pgsql": "public",
"mysql": "sakila",
}
constraints := map[string]string{
"pgsql": "film%",
"mysql": "film%",
}
expected := map[string]string{
"pgsql": "film.2200_16417_10_not_null, film.2200_16417_12_not_null, film.2200_16417_14_not_null, film.2200_16417_1_not_null, film.2200_16417_2_not_null, film.2200_16417_5_not_null, film.2200_16417_7_not_null, film.2200_16417_8_not_null, film.film_language_id_fkey, film.film_original_language_id_fkey, film.film_pkey, film_actor.2200_16429_1_not_null, film_actor.2200_16429_2_not_null, film_actor.2200_16429_3_not_null, film_actor.film_actor_actor_id_fkey, film_actor.film_actor_film_id_fkey, film_actor.film_actor_pkey, film_category.2200_16433_1_not_null, film_category.2200_16433_2_not_null, film_category.2200_16433_3_not_null, film_category.film_category_category_id_fkey, film_category.film_category_film_id_fkey, film_category.film_category_pkey",
"mysql": "film.fk_film_language, film.fk_film_language_original, film.PRIMARY, film_actor.fk_film_actor_actor, film_actor.fk_film_actor_film, film_actor.PRIMARY, film_category.fk_film_category_category, film_category.fk_film_category_film, film_category.PRIMARY, film_text.PRIMARY",
}
for dbName, db := range dbs {
if schemas[dbName] == "" {
continue
}
r := infos.New(db.Opts...)(db.DB).(metadata.ConstraintReader)
result, err := r.Constraints(metadata.Filter{Schema: schemas[dbName], Parent: constraints[dbName]})
if err != nil {
log.Fatalf("Could not read %s constraints: %v", dbName, err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Table+"."+result.Get().Name)
}
actual := strings.Join(names, ", ")
if actual != expected[dbName] {
t.Errorf("Wrong %s constraint names, expected:\n %v\ngot:\n %v", dbName, expected[dbName], names)
}
}
}
func TestConstraintColumns(t *testing.T) {
schemas := map[string]string{
"pgsql": "public",
"mysql": "sakila",
}
constraints := map[string]string{
"pgsql": "film%",
"mysql": "film%",
}
expected := map[string]string{
"pgsql": "actor_id, category_id, film_id, film_id, language_id, original_language_id, film_id, film_id, actor_id, film_id, actor_id, actor_id, film_id, film_id, category_id, film_id, category_id, film_id, film_id, category_id, language_id, language_id",
"mysql": "",
}
for dbName, db := range dbs {
if schemas[dbName] == "" {
continue
}
r := infos.New(db.Opts...)(db.DB).(metadata.ConstraintColumnReader)
result, err := r.ConstraintColumns(metadata.Filter{Schema: schemas[dbName], Name: constraints[dbName]})
if err != nil {
log.Fatalf("Could not read %s constraint columns: %v", dbName, err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Name)
}
actual := strings.Join(names, ", ")
if actual != expected[dbName] {
t.Errorf("Wrong %s constraint column names, expected:\n %v, got:\n %v", dbName, expected[dbName], names)
}
}
}
func TestReverseConstraints(t *testing.T) {
schemas := map[string]string{
"pgsql": "public",
"mysql": "sakila",
}
constraints := map[string]string{
"pgsql": "film%",
"mysql": "film%",
}
expected := map[string]string{
"pgsql": "film_actor.film_actor_film_id_fkey, film_category.film_category_film_id_fkey, inventory.inventory_film_id_fkey",
"mysql": "film_actor.fk_film_actor_film, film_category.fk_film_category_film, inventory.fk_inventory_film",
}
for dbName, db := range dbs {
if schemas[dbName] == "" {
continue
}
r := infos.New(db.Opts...)(db.DB).(metadata.ConstraintReader)
result, err := r.Constraints(metadata.Filter{Schema: schemas[dbName], Reference: constraints[dbName]})
if err != nil {
log.Fatalf("Could not read %s constraints: %v", dbName, err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Table+"."+result.Get().Name)
}
actual := strings.Join(names, ", ")
if actual != expected[dbName] {
t.Errorf("Wrong %s reverse constraint names, expected:\n %v\ngot:\n %v", dbName, expected[dbName], names)
}
}
}
func TestSequences(t *testing.T) {
schemas := map[string]string{
"pgsql": "public",
}
expected := map[string]string{
"pgsql": "actor_actor_id_seq, address_address_id_seq, category_category_id_seq, city_city_id_seq, country_country_id_seq, customer_customer_id_seq, film_film_id_seq, inventory_inventory_id_seq, language_language_id_seq, payment_payment_id_seq, rental_rental_id_seq, staff_staff_id_seq, store_store_id_seq",
}
for dbName, db := range dbs {
if schemas[dbName] == "" {
continue
}
r := infos.New(db.Opts...)(db.DB).(metadata.SequenceReader)
result, err := r.Sequences(metadata.Filter{Schema: schemas[dbName]})
if err != nil {
log.Fatalf("Could not read %s sequences: %v", dbName, err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Name)
}
actual := strings.Join(names, ", ")
if actual != expected[dbName] {
t.Errorf("Wrong %s sequence names, expected:\n %v\ngot:\n %v", dbName, expected[dbName], names)
}
}
}
func TestPrivilegeSummaries_NonExistent(t *testing.T) {
type test struct {
Name string
Db *Database
Schema string
}
tests := map[string]test{
"pgsql": {Db: dbs["pgsql"], Schema: "public"},
"mysql": {Db: dbs["mysql"], Schema: "sakila"},
"sqlserver": {Db: dbs["sqlserver"], Schema: "dbo"},
}
for testName, test := range tests {
if test.Db != nil {
t.Run(testName, func(t *testing.T) {
table := "privtest_table"
// Read privileges
r := infos.New(test.Db.Opts...)(test.Db.DB).(metadata.PrivilegeSummaryReader)
result, err := r.PrivilegeSummaries(metadata.Filter{Schema: test.Schema, Name: table})
if err != nil {
t.Fatalf("Could not read privileges: %v", err)
}
// Check result
if result.Len() != 0 {
t.Errorf("Wrong result count, expected:\n %d, got:\n %d", 0, result.Len())
}
})
}
}
}
func TestPrivilegeSummaries(t *testing.T) {
type test struct {
Db *Database
Schema string
User string
Create string
CreateUserStmt string
DropUserStmt string
Grants []string
WantTable metadata.ObjectPrivileges
WantColumn metadata.ColumnPrivileges
}
setDefaults := func(t test) test {
if t.User == "" {
t.User = "privtest_user"
}
if t.Create == "" {
t.Create = "TABLE"
}
if t.CreateUserStmt == "" {
t.CreateUserStmt = "CREATE USER %s"
}
if t.DropUserStmt == "" {
t.DropUserStmt = "DROP USER %s"
}
if t.Grants == nil {
t.Grants = []string{}
}
if t.WantTable == nil {
t.WantTable = metadata.ObjectPrivileges{}
}
if t.WantColumn == nil {
t.WantColumn = metadata.ColumnPrivileges{}
}
return t
}
postgresDefaultTable := func() metadata.ObjectPrivileges {
return metadata.ObjectPrivileges{
metadata.ObjectPrivilege{Grantee: "postgres", Grantor: "postgres", IsGrantable: true, PrivilegeType: "INSERT"},
metadata.ObjectPrivilege{Grantee: "postgres", Grantor: "postgres", IsGrantable: true, PrivilegeType: "SELECT"},
metadata.ObjectPrivilege{Grantee: "postgres", Grantor: "postgres", IsGrantable: true, PrivilegeType: "UPDATE"},
metadata.ObjectPrivilege{Grantee: "postgres", Grantor: "postgres", IsGrantable: true, PrivilegeType: "DELETE"},
metadata.ObjectPrivilege{Grantee: "postgres", Grantor: "postgres", IsGrantable: true, PrivilegeType: "TRUNCATE"},
metadata.ObjectPrivilege{Grantee: "postgres", Grantor: "postgres", IsGrantable: true, PrivilegeType: "REFERENCES"},
metadata.ObjectPrivilege{Grantee: "postgres", Grantor: "postgres", IsGrantable: true, PrivilegeType: "TRIGGER"},
}
}
postgresDefaultColumn := func(columns []string) metadata.ColumnPrivileges {
p := metadata.ColumnPrivileges{}
for _, col := range columns {
p = append(p,
metadata.ColumnPrivilege{Column: col, Grantee: "postgres", Grantor: "postgres", IsGrantable: true, PrivilegeType: "INSERT"},
metadata.ColumnPrivilege{Column: col, Grantee: "postgres", Grantor: "postgres", IsGrantable: true, PrivilegeType: "SELECT"},
metadata.ColumnPrivilege{Column: col, Grantee: "postgres", Grantor: "postgres", IsGrantable: true, PrivilegeType: "UPDATE"},
metadata.ColumnPrivilege{Column: col, Grantee: "postgres", Grantor: "postgres", IsGrantable: true, PrivilegeType: "REFERENCES"})
}
return p
}
tests := map[string]test{
"pgsql-no-grants": setDefaults(test{
Db: dbs["pgsql"],
Schema: "public",
Grants: []string{},
WantTable: postgresDefaultTable(),
WantColumn: postgresDefaultColumn([]string{"col1", "col2"}),
}),
"pgsql-sequence": setDefaults(test{
Db: dbs["pgsql"],
Schema: "public",
Create: "SEQUENCE",
Grants: []string{"USAGE"},
WantTable: metadata.ObjectPrivileges{
metadata.ObjectPrivilege{Grantee: "privtest_user", Grantor: "postgres", IsGrantable: false, PrivilegeType: "USAGE"},
metadata.ObjectPrivilege{Grantee: "postgres", Grantor: "postgres", IsGrantable: true, PrivilegeType: "USAGE"},
},
}),
"pgsql-view": setDefaults(test{
Db: dbs["pgsql"],
Schema: "public",
Create: "VIEW",
Grants: []string{"SELECT", "INSERT*"},
WantTable: append(postgresDefaultTable(),
metadata.ObjectPrivilege{Grantee: "privtest_user", Grantor: "postgres", IsGrantable: false, PrivilegeType: "SELECT"},
metadata.ObjectPrivilege{Grantee: "privtest_user", Grantor: "postgres", IsGrantable: true, PrivilegeType: "INSERT"},
),
WantColumn: append(
postgresDefaultColumn([]string{"col1", "col2"}),
metadata.ColumnPrivilege{Column: "col1", Grantee: "privtest_user", Grantor: "postgres", IsGrantable: false, PrivilegeType: "SELECT"},
metadata.ColumnPrivilege{Column: "col1", Grantee: "privtest_user", Grantor: "postgres", IsGrantable: true, PrivilegeType: "INSERT"},
metadata.ColumnPrivilege{Column: "col2", Grantee: "privtest_user", Grantor: "postgres", IsGrantable: false, PrivilegeType: "SELECT"},
metadata.ColumnPrivilege{Column: "col2", Grantee: "privtest_user", Grantor: "postgres", IsGrantable: true, PrivilegeType: "INSERT"},
),
}),
"pgsql-table": setDefaults(test{
Db: dbs["pgsql"],
Schema: "public",
Grants: []string{"SELECT", "INSERT*"},
WantTable: append(postgresDefaultTable(),
metadata.ObjectPrivilege{Grantee: "privtest_user", Grantor: "postgres", IsGrantable: false, PrivilegeType: "SELECT"},
metadata.ObjectPrivilege{Grantee: "privtest_user", Grantor: "postgres", IsGrantable: true, PrivilegeType: "INSERT"},
),
WantColumn: append(
postgresDefaultColumn([]string{"col1", "col2"}),
metadata.ColumnPrivilege{Column: "col1", Grantee: "privtest_user", Grantor: "postgres", IsGrantable: false, PrivilegeType: "SELECT"},
metadata.ColumnPrivilege{Column: "col1", Grantee: "privtest_user", Grantor: "postgres", IsGrantable: true, PrivilegeType: "INSERT"},
metadata.ColumnPrivilege{Column: "col2", Grantee: "privtest_user", Grantor: "postgres", IsGrantable: false, PrivilegeType: "SELECT"},
metadata.ColumnPrivilege{Column: "col2", Grantee: "privtest_user", Grantor: "postgres", IsGrantable: true, PrivilegeType: "INSERT"},
),
}),
"pgsql-column": setDefaults(test{
Db: dbs["pgsql"],
Schema: "public",
Grants: []string{"SELECT(col1)", "INSERT(col2)*"},
WantTable: postgresDefaultTable(),
WantColumn: append(
postgresDefaultColumn([]string{"col1", "col2"}),
metadata.ColumnPrivilege{Column: "col1", Grantee: "privtest_user", Grantor: "postgres", IsGrantable: false, PrivilegeType: "SELECT"},
metadata.ColumnPrivilege{Column: "col2", Grantee: "privtest_user", Grantor: "postgres", IsGrantable: true, PrivilegeType: "INSERT"},
),
}),
"pgsql-table-column": setDefaults(test{
Db: dbs["pgsql"],
Schema: "public",
Grants: []string{"SELECT", "INSERT(col1)"},
WantTable: append(postgresDefaultTable(),
metadata.ObjectPrivilege{Grantee: "privtest_user", Grantor: "postgres", IsGrantable: false, PrivilegeType: "SELECT"},
),
WantColumn: append(
postgresDefaultColumn([]string{"col1", "col2"}),
metadata.ColumnPrivilege{Column: "col1", Grantee: "privtest_user", Grantor: "postgres", IsGrantable: false, PrivilegeType: "SELECT"},
metadata.ColumnPrivilege{Column: "col1", Grantee: "privtest_user", Grantor: "postgres", IsGrantable: false, PrivilegeType: "INSERT"},
metadata.ColumnPrivilege{Column: "col2", Grantee: "privtest_user", Grantor: "postgres", IsGrantable: false, PrivilegeType: "SELECT"},
),
}),
"mysql-no-grants": setDefaults(test{
Db: dbs["mysql"],
Schema: "sakila",
User: "'privtest_user'@'%'",
Grants: []string{},
WantTable: metadata.ObjectPrivileges{},
}),
"mysql-view": setDefaults(test{
Db: dbs["mysql"],
Schema: "sakila",
Create: "VIEW",
User: "'privtest_user'@'%'",
Grants: []string{"SELECT", "INSERT"},
WantTable: metadata.ObjectPrivileges{
metadata.ObjectPrivilege{Grantee: "'privtest_user'@'%'", Grantor: "", IsGrantable: false, PrivilegeType: "SELECT"},
metadata.ObjectPrivilege{Grantee: "'privtest_user'@'%'", Grantor: "", IsGrantable: false, PrivilegeType: "INSERT"},
},
}),
"mysql-view-grantable": setDefaults(test{
Db: dbs["mysql"],
Schema: "sakila",
Create: "VIEW",
User: "'privtest_user'@'%'",
Grants: []string{"SELECT*", "INSERT*"},
WantTable: metadata.ObjectPrivileges{
metadata.ObjectPrivilege{Grantee: "'privtest_user'@'%'", Grantor: "", IsGrantable: true, PrivilegeType: "SELECT"},
metadata.ObjectPrivilege{Grantee: "'privtest_user'@'%'", Grantor: "", IsGrantable: true, PrivilegeType: "INSERT"},
},
}),
"mysql-table": setDefaults(test{
Db: dbs["mysql"],
Schema: "sakila",
User: "'privtest_user'@'%'",
Grants: []string{"SELECT", "INSERT"},
WantTable: metadata.ObjectPrivileges{
metadata.ObjectPrivilege{Grantee: "'privtest_user'@'%'", Grantor: "", IsGrantable: false, PrivilegeType: "SELECT"},
metadata.ObjectPrivilege{Grantee: "'privtest_user'@'%'", Grantor: "", IsGrantable: false, PrivilegeType: "INSERT"},
},
}),
"mysql-table-grantable": setDefaults(test{
Db: dbs["mysql"],
Schema: "sakila",
User: "'privtest_user'@'%'",
Grants: []string{"SELECT*", "INSERT*"},
WantTable: metadata.ObjectPrivileges{
metadata.ObjectPrivilege{Grantee: "'privtest_user'@'%'", Grantor: "", IsGrantable: true, PrivilegeType: "SELECT"},
metadata.ObjectPrivilege{Grantee: "'privtest_user'@'%'", Grantor: "", IsGrantable: true, PrivilegeType: "INSERT"},
},
}),
"mysql-column": setDefaults(test{
Db: dbs["mysql"],
Schema: "sakila",
User: "'privtest_user'@'%'",
Grants: []string{"SELECT(col1)", "INSERT(col2)"},
WantColumn: metadata.ColumnPrivileges{
metadata.ColumnPrivilege{Column: "col1", Grantee: "'privtest_user'@'%'", Grantor: "", IsGrantable: false, PrivilegeType: "SELECT"},
metadata.ColumnPrivilege{Column: "col2", Grantee: "'privtest_user'@'%'", Grantor: "", IsGrantable: false, PrivilegeType: "INSERT"},
},
}),
"mysql-column-grantable": setDefaults(test{
Db: dbs["mysql"],
Schema: "sakila",
User: "'privtest_user'@'%'",
Grants: []string{"SELECT(col1)*", "INSERT(col2)*"},
WantColumn: metadata.ColumnPrivileges{
metadata.ColumnPrivilege{Column: "col1", Grantee: "'privtest_user'@'%'", Grantor: "", IsGrantable: true, PrivilegeType: "SELECT"},
metadata.ColumnPrivilege{Column: "col2", Grantee: "'privtest_user'@'%'", Grantor: "", IsGrantable: true, PrivilegeType: "INSERT"},
},
}),
"mysql-table-column": setDefaults(test{
Db: dbs["mysql"],
Schema: "sakila",
User: "'privtest_user'@'%'",
Grants: []string{"SELECT", "INSERT(col1)"},
WantTable: metadata.ObjectPrivileges{
metadata.ObjectPrivilege{Grantee: "'privtest_user'@'%'", Grantor: "", IsGrantable: false, PrivilegeType: "SELECT"},
},
WantColumn: metadata.ColumnPrivileges{
metadata.ColumnPrivilege{Column: "col1", Grantee: "'privtest_user'@'%'", Grantor: "", IsGrantable: false, PrivilegeType: "INSERT"},
},
}),
"sqlserver-no-grants": setDefaults(test{
Db: dbs["sqlserver"],
Schema: "dbo",
CreateUserStmt: "CREATE LOGIN %[1]s WITH PASSWORD = 'yourStrong123_Password'; CREATE USER %[1]s FOR LOGIN %[1]s",
DropUserStmt: "DROP USER %[1]s; DROP LOGIN %[1]s",
Grants: []string{},
WantTable: metadata.ObjectPrivileges{},
}),
"sqlserver-view": setDefaults(test{
Db: dbs["sqlserver"],
Schema: "dbo",
Create: "VIEW",
CreateUserStmt: "CREATE LOGIN %[1]s WITH PASSWORD = 'yourStrong123_Password'; CREATE USER %[1]s FOR LOGIN %[1]s",
DropUserStmt: "DROP USER %[1]s; DROP LOGIN %[1]s",
Grants: []string{"SELECT", "INSERT*"},
WantTable: metadata.ObjectPrivileges{
metadata.ObjectPrivilege{Grantee: "privtest_user", Grantor: "dbo", IsGrantable: false, PrivilegeType: "SELECT"},
metadata.ObjectPrivilege{Grantee: "privtest_user", Grantor: "dbo", IsGrantable: true, PrivilegeType: "INSERT"},
},
}),
"sqlserver-table": setDefaults(test{
Db: dbs["sqlserver"],
Schema: "dbo",
CreateUserStmt: "CREATE LOGIN %[1]s WITH PASSWORD = 'yourStrong123_Password'; CREATE USER %[1]s FOR LOGIN %[1]s",
DropUserStmt: "DROP USER %[1]s; DROP LOGIN %[1]s",
Grants: []string{"SELECT", "INSERT*"},
WantTable: metadata.ObjectPrivileges{
metadata.ObjectPrivilege{Grantee: "privtest_user", Grantor: "dbo", IsGrantable: false, PrivilegeType: "SELECT"},
metadata.ObjectPrivilege{Grantee: "privtest_user", Grantor: "dbo", IsGrantable: true, PrivilegeType: "INSERT"},
},
}),
"sqlserver-column": setDefaults(test{
Db: dbs["sqlserver"],
Schema: "dbo",
CreateUserStmt: "CREATE LOGIN %[1]s WITH PASSWORD = 'yourStrong123_Password'; CREATE USER %[1]s FOR LOGIN %[1]s",
DropUserStmt: "DROP USER %[1]s; DROP LOGIN %[1]s",
Grants: []string{"SELECT(col1)", "UPDATE(col2)*"},
WantColumn: metadata.ColumnPrivileges{
metadata.ColumnPrivilege{Column: "col1", Grantee: "privtest_user", Grantor: "dbo", IsGrantable: false, PrivilegeType: "SELECT"},
metadata.ColumnPrivilege{Column: "col2", Grantee: "privtest_user", Grantor: "dbo", IsGrantable: true, PrivilegeType: "UPDATE"},
},
}),
"sqlserver-table-column": setDefaults(test{
Db: dbs["sqlserver"],
Schema: "dbo",
CreateUserStmt: "CREATE LOGIN %[1]s WITH PASSWORD = 'yourStrong123_Password'; CREATE USER %[1]s FOR LOGIN %[1]s",
DropUserStmt: "DROP USER %[1]s; DROP LOGIN %[1]s",
Grants: []string{"SELECT", "UPDATE(col1)"},
WantTable: metadata.ObjectPrivileges{
metadata.ObjectPrivilege{Grantee: "privtest_user", Grantor: "dbo", IsGrantable: false, PrivilegeType: "SELECT"},
},
WantColumn: metadata.ColumnPrivileges{
metadata.ColumnPrivilege{Column: "col1", Grantee: "privtest_user", Grantor: "dbo", IsGrantable: false, PrivilegeType: "UPDATE"},
},
}),
}
for testName, test := range tests {
if test.Db != nil {
t.Run(testName, func(t *testing.T) {
// Create user, table and grants
const name = "privtest"
var query string
var err error
query = fmt.Sprintf(test.CreateUserStmt, test.User)
_, err = test.Db.DB.Exec(query)
if err != nil {
t.Fatalf("Could not CREATE USER:\n%s\n%s", query, err)
}
defer test.Db.DB.Exec(fmt.Sprintf(test.DropUserStmt, test.User))
switch test.Create {
case "TABLE":
query = fmt.Sprintf("CREATE TABLE %s.%s (col1 int, col2 varchar(255))", test.Schema, name)
_, err = test.Db.DB.Exec(query)
if err != nil {
t.Fatalf("Could not CREATE TABLE:\n%s\n%s", query, err)
}
defer test.Db.DB.Exec(fmt.Sprintf("DROP TABLE %s.%s", test.Schema, name))
case "VIEW":
query = fmt.Sprintf("CREATE TABLE %s.%s_table (col1 int, col2 varchar(255))", test.Schema, name)
_, err = test.Db.DB.Exec(query)
if err != nil {
t.Fatalf("Could not CREATE TABLE:\n%s\n%s", query, err)
}
defer test.Db.DB.Exec(fmt.Sprintf("DROP TABLE %s.%s_table", test.Schema, name))
query = fmt.Sprintf("CREATE VIEW %s.%s AS SELECT * FROM %[1]s.%[2]s_table", test.Schema, name)
_, err = test.Db.DB.Exec(query)
if err != nil {
t.Fatalf("Could not CREATE VIEW:\n%s\n%s", query, err)
}
defer test.Db.DB.Exec(fmt.Sprintf("DROP VIEW %s.%s", test.Schema, name))
case "SEQUENCE":
query = fmt.Sprintf("CREATE SEQUENCE %s.%s", test.Schema, name)
_, err = test.Db.DB.Exec(query)
if err != nil {
t.Fatalf("Could not CREATE SEQUENCE:\n%s\n%s", query, err)
}
defer test.Db.DB.Exec(fmt.Sprintf("DROP SEQUENCE %s.%s", test.Schema, name))
}
for _, grant := range test.Grants {
isGrantable := false
if grant[len(grant)-1] == '*' {
isGrantable = true
grant = grant[:len(grant)-1]
}
query = fmt.Sprintf("GRANT %s ON %s.%s TO %s", grant, test.Schema, name, test.User)
if isGrantable {
query += " WITH GRANT OPTION"
}
_, err = test.Db.DB.Exec(query)
if err != nil {
t.Fatalf("Could not GRANT %s:\n%s\n%s", grant, query, err)
}
}
// Read privileges
r := infos.New(test.Db.Opts...)(test.Db.DB).(metadata.PrivilegeSummaryReader)
types := []string{"TABLE", "BASE TABLE", "SYSTEM TABLE", "SYNONYM", "LOCAL TEMPORARY", "GLOBAL TEMPORARY", "VIEW", "SYSTEM VIEW", "MATERIALIZED VIEW", "SEQUENCE"}
result, err := r.PrivilegeSummaries(metadata.Filter{Schema: test.Schema, Name: name, Types: types})
if err != nil {
t.Fatalf("Could not read privileges: %v", err)
}
// Check result
if result.Len() != 1 {
t.Fatalf("Wrong result count\nWant:\t%d\nGot:\t%d\n", 1, result.Len())
}
result.Next()
if result.Get().Schema != test.Schema {
t.Errorf("Wrong schema!\nWant:\t%s\nGot:\t%s\n", test.Schema, result.Get().Schema)
}
if result.Get().Name != name {
t.Errorf("Wrong table!\nWant:\t%s\nGot:\t%s\n", name, result.Get().Name)
}
want := ""
switch test.Create {
case "TABLE":
want = "BASE TABLE"
default:
want = test.Create
}
if result.Get().ObjectType != want {
t.Errorf("Wrong Type!\nWant:\t%s\nGot:\t%s\n", want, result.Get().ObjectType)
}
gotTablePrivileges := result.Get().ObjectPrivileges
sort.Sort(gotTablePrivileges)
sort.Sort(test.WantTable)
if diff := cmp.Diff(test.WantTable, gotTablePrivileges); diff != "" {
t.Errorf("Wrong object privileges!\n(-expected, +got):\n%s", diff)
}
gotColumnPrivileges := result.Get().ColumnPrivileges
sort.Sort(gotColumnPrivileges)
sort.Sort(test.WantColumn)
if diff := cmp.Diff(test.WantColumn, gotColumnPrivileges); diff != "" {
t.Errorf("Wrong column privileges!\n(-expected, +got):\n%s", diff)
}
})
}
}
}
================================================
FILE: drivers/metadata/metadata.go
================================================
package metadata
import (
"strings"
"github.com/xo/dburl"
"github.com/xo/usql/text"
)
// ExtendedReader of all database metadata in a structured format.
type ExtendedReader interface {
CatalogReader
SchemaReader
TableReader
ColumnReader
ColumnStatReader
IndexReader
IndexColumnReader
TriggerReader
ConstraintReader
ConstraintColumnReader
FunctionReader
FunctionColumnReader
SequenceReader
PrivilegeSummaryReader
}
// BasicReader of common database metadata like schemas, tables and columns.
type BasicReader interface {
SchemaReader
TableReader
ColumnReader
}
// CatalogReader lists database schemas.
type CatalogReader interface {
Reader
Catalogs(Filter) (*CatalogSet, error)
}
// SchemaReader lists database schemas.
type SchemaReader interface {
Reader
Schemas(Filter) (*SchemaSet, error)
}
// TableReader lists database tables.
type TableReader interface {
Reader
Tables(Filter) (*TableSet, error)
}
// ColumnReader lists table columns.
type ColumnReader interface {
Reader
Columns(Filter) (*ColumnSet, error)
}
// ColumnStatsReader lists table column statistics.
type ColumnStatReader interface {
Reader
ColumnStats(Filter) (*ColumnStatSet, error)
}
// IndexReader lists table indexes.
type IndexReader interface {
Reader
Indexes(Filter) (*IndexSet, error)
}
// IndexColumnReader lists index columns.
type IndexColumnReader interface {
Reader
IndexColumns(Filter) (*IndexColumnSet, error)
}
// TriggerReader lists table triggers.
type TriggerReader interface {
Reader
Triggers(Filter) (*TriggerSet, error)
}
// ConstraintReader lists table constraints.
type ConstraintReader interface {
Reader
Constraints(Filter) (*ConstraintSet, error)
}
// ConstraintColumnReader lists constraint columns.
type ConstraintColumnReader interface {
Reader
ConstraintColumns(Filter) (*ConstraintColumnSet, error)
}
// FunctionReader lists database functions.
type FunctionReader interface {
Reader
Functions(Filter) (*FunctionSet, error)
}
// FunctionColumnReader lists function parameters.
type FunctionColumnReader interface {
Reader
FunctionColumns(Filter) (*FunctionColumnSet, error)
}
// SequenceReader lists sequences.
type SequenceReader interface {
Reader
Sequences(Filter) (*SequenceSet, error)
}
// PrivilegeSummaryReader lists summaries of privileges granted on tables, views and sequences.
type PrivilegeSummaryReader interface {
Reader
PrivilegeSummaries(Filter) (*PrivilegeSummarySet, error)
}
// Reader of any database metadata in a structured format.
type Reader interface{}
// Filter objects returned by Readers
type Filter struct {
// Catalog name pattern that objects must belong to;
// use Name to filter catalogs by name
Catalog string
// Schema name pattern that objects must belong to;
// use Name to filter schemas by name
Schema string
// Parent name pattern that objects must belong to;
// does not apply to schema and catalog containing matching objects
Parent string
// Reference name pattern of other objects referencing this one,
Reference string
// Name pattern that object name must match
Name string
// Types of the object
Types []string
// WithSystem objects
WithSystem bool
// OnlyVisible objects
OnlyVisible bool
}
// Writer of database metadata in a human readable format.
type Writer interface {
// DescribeFunctions \df, \dfa, \dfn, \dft, \dfw, etc.
DescribeFunctions(*dburl.URL, string, string, bool, bool) error
// DescribeTableDetails \d foo
DescribeTableDetails(*dburl.URL, string, bool, bool) error
// ListAllDbs \l
ListAllDbs(*dburl.URL, string, bool) error
// ListTables \dt, \dv, \dm, etc.
ListTables(*dburl.URL, string, string, bool, bool) error
// ListSchemas \dn
ListSchemas(*dburl.URL, string, bool, bool) error
// ListIndexes \di
ListIndexes(*dburl.URL, string, bool, bool) error
// ShowStats \ss
ShowStats(*dburl.URL, string, string, bool, int) error
// ListPrivilegeSummaries \dp
ListPrivilegeSummaries(*dburl.URL, string, bool) error
}
type CatalogSet struct {
resultSet
}
func NewCatalogSet(v []Catalog) *CatalogSet {
r := make([]Result, len(v))
for i := range v {
r[i] = &v[i]
}
return &CatalogSet{
resultSet: resultSet{
results: r,
columns: []string{"Catalog"},
},
}
}
func NewCatalogSetWithColumns(v []Result, cols []string) *CatalogSet {
return &CatalogSet{
resultSet: resultSet{
results: v,
columns: cols,
},
}
}
type CatalogProvider interface {
GetCatalog() Catalog
}
func (s CatalogSet) Get() Catalog {
r := s.results[s.current-1]
return r.(CatalogProvider).GetCatalog()
}
type Catalog struct {
Catalog string
}
func (s Catalog) Values() []interface{} {
return []interface{}{s.Catalog}
}
func (s Catalog) GetCatalog() Catalog {
return s
}
type SchemaSet struct {
resultSet
}
func NewSchemaSet(v []Schema) *SchemaSet {
r := make([]Result, len(v))
for i := range v {
r[i] = &v[i]
}
return &SchemaSet{
resultSet: resultSet{
results: r,
columns: []string{"Schema", "Catalog"},
},
}
}
func (s SchemaSet) Get() *Schema {
return s.results[s.current-1].(*Schema)
}
type Schema struct {
Schema string
Catalog string
}
func (s Schema) Values() []interface{} {
return []interface{}{s.Schema, s.Catalog}
}
type TableSet struct {
resultSet
}
func NewTableSet(v []Table) *TableSet {
r := make([]Result, len(v))
for i := range v {
r[i] = &v[i]
}
return &TableSet{
resultSet: resultSet{
results: r,
columns: []string{
"Catalog",
"Schema",
"Name",
"Type",
"Rows",
"Size",
"Comment",
},
},
}
}
func (t TableSet) Get() *Table {
return t.results[t.current-1].(*Table)
}
type Table struct {
Catalog string
Schema string
Name string
Type string
Rows int64
Size string
Comment string
}
func (t Table) Values() []interface{} {
return []interface{}{
t.Catalog,
t.Schema,
t.Name,
t.Type,
t.Rows,
t.Size,
t.Comment,
}
}
type ColumnSet struct {
resultSet
}
func NewColumnSet(v []Column) *ColumnSet {
r := make([]Result, len(v))
for i := range v {
r[i] = &v[i]
}
return &ColumnSet{
resultSet: resultSet{
results: r,
columns: []string{
"Catalog",
"Schema",
"Table",
"Name",
"Type",
"Nullable",
"Default",
"Size",
"Decimal Digits",
"Precision Radix",
"Octet Length",
},
},
}
}
func (c ColumnSet) Get() *Column {
return c.results[c.current-1].(*Column)
}
type Column struct {
Catalog string
Schema string
Table string
Name string
OrdinalPosition int
DataType string
// ScanType reflect.Type
Default string
ColumnSize int
DecimalDigits int
NumPrecRadix int
CharOctetLength int
IsNullable Bool
}
type Bool string
var (
UNKNOWN Bool = ""
YES Bool = "YES"
NO Bool = "NO"
)
func (c Column) Values() []interface{} {
return []interface{}{
c.Catalog,
c.Schema,
c.Table,
c.Name,
c.DataType,
c.IsNullable,
c.Default,
c.ColumnSize,
c.DecimalDigits,
c.NumPrecRadix,
c.CharOctetLength,
}
}
type ColumnStatSet struct {
resultSet
}
func NewColumnStatSet(v []ColumnStat) *ColumnStatSet {
r := make([]Result, len(v))
for i := range v {
r[i] = &v[i]
}
return &ColumnStatSet{
resultSet: resultSet{
results: r,
columns: []string{
"Catalog",
"Schema",
"Table",
"Name",
"Average width",
"Nulls fraction",
"Distinct values",
"Minimum value",
"Maximum value",
"Mean value",
"Top N common values",
"Top N values freqs",
},
},
}
}
func (c ColumnStatSet) Get() *ColumnStat {
return c.results[c.current-1].(*ColumnStat)
}
type ColumnStat struct {
Catalog string
Schema string
Table string
Name string
AvgWidth int
NullFrac float64
NumDistinct int64
Min string
Max string
Mean string
TopN []string
TopNFreqs []float64
}
func (c ColumnStat) Values() []interface{} {
return []interface{}{
c.Catalog,
c.Schema,
c.Table,
c.Name,
c.AvgWidth,
c.NullFrac,
c.NumDistinct,
c.Min,
c.Max,
c.Mean,
c.TopN,
c.TopNFreqs,
}
}
type IndexSet struct {
resultSet
}
func NewIndexSet(v []Index) *IndexSet {
r := make([]Result, len(v))
for i := range v {
r[i] = &v[i]
}
return &IndexSet{
resultSet: resultSet{
results: r,
columns: []string{
"Catalog",
"Schema",
"Name",
"Table",
"Is primary",
"Is unique",
"Type",
},
},
}
}
func (i IndexSet) Get() *Index {
return i.results[i.current-1].(*Index)
}
type Index struct {
Catalog string
Schema string
Table string
Name string
IsPrimary Bool
IsUnique Bool
Type string
Columns string
}
func (i Index) Values() []interface{} {
return []interface{}{
i.Catalog,
i.Schema,
i.Name,
i.Table,
i.IsPrimary,
i.IsUnique,
i.Type,
}
}
type IndexColumnSet struct {
resultSet
}
func NewIndexColumnSet(v []IndexColumn) *IndexColumnSet {
r := make([]Result, len(v))
for i := range v {
r[i] = &v[i]
}
return &IndexColumnSet{
resultSet: resultSet{
results: r,
columns: []string{
"Catalog",
"Schema",
"Table",
"Index name",
"Name",
"Data type",
},
},
}
}
func (c IndexColumnSet) Get() *IndexColumn {
return c.results[c.current-1].(*IndexColumn)
}
type IndexColumn struct {
Catalog string
Schema string
Table string
IndexName string
Name string
DataType string
OrdinalPosition int
}
func (c IndexColumn) Values() []interface{} {
return []interface{}{
c.Catalog,
c.Schema,
c.Table,
c.IndexName,
c.Name,
c.DataType,
}
}
type ConstraintSet struct {
resultSet
}
func NewConstraintSet(v []Constraint) *ConstraintSet {
r := make([]Result, len(v))
for i := range v {
r[i] = &v[i]
}
return &ConstraintSet{
resultSet: resultSet{
results: r,
columns: []string{
"Catalog",
"Schema",
"Table",
"Name",
"Type",
"Is deferrable",
"Initially deferred",
"Foreign catalog",
"Foreign schema",
"Foreign table",
"Foreign name",
"Match type",
"Update rule",
"Delete rule",
"Check Clause",
},
},
}
}
func (i ConstraintSet) Get() *Constraint {
return i.results[i.current-1].(*Constraint)
}
type Constraint struct {
Catalog string
Schema string
Table string
Name string
Type string
IsDeferrable Bool
IsInitiallyDeferred Bool
ForeignCatalog string
ForeignSchema string
ForeignTable string
ForeignName string
MatchType string
UpdateRule string
DeleteRule string
CheckClause string
}
func (i Constraint) Values() []interface{} {
return []interface{}{
i.Catalog,
i.Schema,
i.Table,
i.Name,
i.Type,
i.IsDeferrable,
i.IsInitiallyDeferred,
i.ForeignCatalog,
i.ForeignSchema,
i.ForeignTable,
i.ForeignName,
i.MatchType,
i.UpdateRule,
i.DeleteRule,
}
}
type ConstraintColumnSet struct {
resultSet
}
func NewConstraintColumnSet(v []ConstraintColumn) *ConstraintColumnSet {
r := make([]Result, len(v))
for i := range v {
r[i] = &v[i]
}
return &ConstraintColumnSet{
resultSet: resultSet{
results: r,
columns: []string{
"Catalog",
"Schema",
"Table",
"Constraint",
"Name",
"Foreign Catalog",
"Foreign Schema",
"Foreign Table",
"Foreign Constraint",
"Foreign Name",
},
},
}
}
func (c ConstraintColumnSet) Get() *ConstraintColumn {
return c.results[c.current-1].(*ConstraintColumn)
}
type ConstraintColumn struct {
Catalog string
Schema string
Table string
Constraint string
Name string
OrdinalPosition int
ForeignCatalog string
ForeignSchema string
ForeignTable string
ForeignConstraint string
ForeignName string
}
func (c ConstraintColumn) Values() []interface{} {
return []interface{}{
c.Catalog,
c.Schema,
c.Table,
c.Constraint,
c.Name,
c.ForeignCatalog,
c.ForeignSchema,
c.ForeignTable,
c.ForeignConstraint,
c.ForeignName,
}
}
type FunctionSet struct {
resultSet
}
func NewFunctionSet(v []Function) *FunctionSet {
r := make([]Result, len(v))
for i := range v {
r[i] = &v[i]
}
return &FunctionSet{
resultSet: resultSet{
results: r,
columns: []string{
"Catalog",
"Schema",
"Name",
"Result data type",
"Argument data types",
"Type",
"Volatility",
"Security",
"Language",
"Source code",
},
},
}
}
func (f FunctionSet) Get() *Function {
return f.results[f.current-1].(*Function)
}
type Function struct {
Catalog string
Schema string
Name string
ResultType string
ArgTypes string
Type string
Volatility string
Security string
Language string
Source string
SpecificName string
}
func (f Function) Values() []interface{} {
return []interface{}{
f.Catalog,
f.Schema,
f.Name,
f.ResultType,
f.ArgTypes,
f.Type,
f.Volatility,
f.Security,
f.Language,
f.Source,
}
}
type FunctionColumnSet struct {
resultSet
}
func NewFunctionColumnSet(v []FunctionColumn) *FunctionColumnSet {
r := make([]Result, len(v))
for i := range v {
r[i] = &v[i]
}
return &FunctionColumnSet{
resultSet: resultSet{
results: r,
columns: []string{
"Catalog",
"Schema",
"Function name",
"Name",
"Type",
"Data type",
"Size",
"Decimal Digits",
"Precision Radix",
"Octet Length",
},
},
}
}
func (c FunctionColumnSet) Get() *FunctionColumn {
return c.results[c.current-1].(*FunctionColumn)
}
type FunctionColumn struct {
Catalog string
Schema string
Table string
Name string
FunctionName string
OrdinalPosition int
Type string
DataType string
// ScanType reflect.Type
ColumnSize int
DecimalDigits int
NumPrecRadix int
CharOctetLength int
}
func (c FunctionColumn) Values() []interface{} {
return []interface{}{
c.Catalog,
c.Schema,
c.FunctionName,
c.Name,
c.Type,
c.DataType,
c.ColumnSize,
c.DecimalDigits,
c.NumPrecRadix,
c.CharOctetLength,
}
}
type SequenceSet struct {
resultSet
}
func NewSequenceSet(v []Sequence) *SequenceSet {
r := make([]Result, len(v))
for i := range v {
r[i] = &v[i]
}
return &SequenceSet{
resultSet: resultSet{
results: r,
columns: []string{
"Type",
"Start",
"Min",
"Max",
"Increment",
"Cycles?",
},
},
}
}
func (s SequenceSet) Get() *Sequence {
return s.results[s.current-1].(*Sequence)
}
type Sequence struct {
Catalog string
Schema string
Name string
DataType string
Start string
Min string
Max string
Increment string
Cycles Bool
}
func (s Sequence) Values() []interface{} {
return []interface{}{
s.DataType,
s.Start,
s.Min,
s.Max,
s.Increment,
s.Cycles,
}
}
type PrivilegeSummarySet struct {
resultSet
}
func NewPrivilegeSummarySet(v []PrivilegeSummary) *PrivilegeSummarySet {
r := make([]Result, len(v))
for i := range v {
r[i] = &v[i]
}
return &PrivilegeSummarySet{
resultSet: resultSet{
results: r,
columns: []string{
"Schema",
"Name",
"Type",
"Access privileges",
"Column privileges",
},
},
}
}
func (s PrivilegeSummarySet) Get() *PrivilegeSummary {
return s.results[s.current-1].(*PrivilegeSummary)
}
// PrivilegeSummary summarizes the privileges granted on a database object
type PrivilegeSummary struct {
Catalog string
Schema string
Name string
ObjectType string
ObjectPrivileges ObjectPrivileges
ColumnPrivileges ColumnPrivileges
}
func (s PrivilegeSummary) Values() []interface{} {
return []interface{}{
s.Catalog,
s.Schema,
s.Name,
s.ObjectType,
s.ObjectPrivileges,
s.ColumnPrivileges,
}
}
// ObjectPrivilege represents a privilege granted on a database object.
type ObjectPrivilege struct {
Grantee string
Grantor string
PrivilegeType string
IsGrantable bool
}
// ColumnPrivilege represents a privilege granted on a column.
type ColumnPrivilege struct {
Column string
Grantee string
Grantor string
PrivilegeType string
IsGrantable bool
}
// ObjectPrivileges represents privileges granted on a database object.
// The privileges are assumed to be sorted. Otherwise the
// String() method will fail.
type ObjectPrivileges []ObjectPrivilege
// ColumnPrivileges represents privileges granted on a column.
// The privileges are assumed to be sorted. Otherwise the
// String() method will fail.
type ColumnPrivileges []ColumnPrivilege
func (p ObjectPrivileges) Len() int { return len(p) }
func (p ObjectPrivileges) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p ObjectPrivileges) Less(i, j int) bool {
switch {
case p[i].Grantee != p[j].Grantee:
return p[i].Grantee < p[j].Grantee
case p[i].Grantor != p[j].Grantor:
return p[i].Grantor < p[j].Grantor
}
return p[i].PrivilegeType < p[j].PrivilegeType
}
// String returns a string representation of ObjectPrivileges.
// Assumes the ObjectPrivileges to be sorted.
func (p ObjectPrivileges) String() string {
if len(p) == 0 {
return ""
}
lines := []string{}
types := []string{}
for i := range p {
switch {
// Is last privilege or next privilege has new grantee or grantor; finalize line
case i == len(p)-1 || p[i].Grantee != p[i+1].Grantee || p[i].Grantor != p[i+1].Grantor:
types = append(types, typeStr(p[i].PrivilegeType, p[i].IsGrantable))
lines = append(lines, lineStr(p[i].Grantee, p[i].Grantor, types))
types = types[:0]
default:
types = append(types, typeStr(p[i].PrivilegeType, p[i].IsGrantable))
}
}
return strings.Join(lines, "\n")
}
func (p ColumnPrivileges) Len() int { return len(p) }
func (p ColumnPrivileges) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p ColumnPrivileges) Less(i, j int) bool {
switch {
case p[i].Column != p[j].Column:
return p[i].Column < p[j].Column
case p[i].Grantee != p[j].Grantee:
return p[i].Grantee < p[j].Grantee
case p[i].Grantor != p[j].Grantor:
return p[i].Grantor < p[j].Grantor
}
return p[i].PrivilegeType < p[j].PrivilegeType
}
// String returns a string representation of ColumnPrivileges.
// Assumes the ColumnPrivileges to be sorted.
func (p ColumnPrivileges) String() string {
if len(p) == 0 {
return ""
}
colBlocks := []string{}
lines := []string{}
types := []string{}
for i := range p {
switch {
// Is last privilege or next privilege has new column; finalize column block
case i == len(p)-1 || p[i].Column != p[i+1].Column:
types = append(types, typeStr(p[i].PrivilegeType, p[i].IsGrantable))
lines = append(lines, " "+lineStr(p[i].Grantee, p[i].Grantor, types))
colBlocks = append(colBlocks, p[i].Column+":\n"+strings.Join(lines, "\n"))
lines = lines[:0]
types = types[:0]
// Next privilege has new grantee or grantor; finalize line
case p[i].Grantee != p[i+1].Grantee || p[i].Grantor != p[i+1].Grantor:
types = append(types, typeStr(p[i].PrivilegeType, p[i].IsGrantable))
lines = append(lines, " "+lineStr(p[i].Grantee, p[i].Grantor, types))
types = types[:0]
default:
types = append(types, typeStr(p[i].PrivilegeType, p[i].IsGrantable))
}
}
return strings.Join(colBlocks, "\n")
}
// typeStr appends an asterisk suffix to grantable privileges
func typeStr(privilege string, grantable bool) string {
if grantable {
return privilege + "*"
} else {
return privilege
}
}
// lineStr compiles grantee, grantor and privilege types into a line of output
func lineStr(grantee, grantor string, types []string) string {
if grantor != "" {
return grantee + "=" + strings.Join(types, ",") + "/" + grantor
} else {
return grantee + "=" + strings.Join(types, ",")
}
}
type resultSet struct {
results []Result
columns []string
current int
filter func(Result) bool
scanValues func(Result) []interface{}
}
type Result interface {
Values() []interface{}
}
func (r *resultSet) SetFilter(f func(Result) bool) {
r.filter = f
}
func (r *resultSet) SetColumns(c []string) {
r.columns = c
}
func (r *resultSet) SetScanValues(s func(Result) []interface{}) {
r.scanValues = s
}
func (r *resultSet) Len() int {
if r.filter == nil {
return len(r.results)
}
len := 0
for _, rec := range r.results {
if r.filter(rec) {
len++
}
}
return len
}
func (r *resultSet) Reset() {
r.current = 0
}
func (r *resultSet) Next() bool {
r.current++
if r.filter != nil {
for r.current <= len(r.results) && !r.filter(r.results[r.current-1]) {
r.current++
}
}
return r.current <= len(r.results)
}
func (r resultSet) Columns() ([]string, error) {
return r.columns, nil
}
func (r resultSet) Scan(dest ...interface{}) error {
var v []interface{}
if r.scanValues == nil {
v = r.results[r.current-1].Values()
} else {
v = r.scanValues(r.results[r.current-1])
}
if len(v) != len(dest) {
return text.ErrWrongNumberOfArguments
}
for i, d := range dest {
p := d.(*interface{})
*p = v[i]
}
return nil
}
func (r resultSet) Close() error {
return nil
}
func (r resultSet) Err() error {
return nil
}
func (r resultSet) NextResultSet() bool {
return false
}
type Trigger struct {
Catalog string
Schema string
Table string
Name string
Definition string
}
func (t Trigger) Values() []interface{} {
return []interface{}{
t.Catalog,
t.Schema,
t.Table,
t.Name,
t.Definition,
}
}
type TriggerSet struct {
resultSet
}
func NewTriggerSet(t []Trigger) *TriggerSet {
r := make([]Result, len(t))
for i := range t {
r[i] = &t[i]
}
return &TriggerSet{
resultSet: resultSet{
results: r,
columns: []string{
"Catalog",
"Schema",
"Table",
"Name",
"Definition",
},
},
}
}
func (t TriggerSet) Get() *Trigger {
return t.results[t.current-1].(*Trigger)
}
================================================
FILE: drivers/metadata/metadata_test.go
================================================
package metadata
import (
"testing"
"github.com/google/go-cmp/cmp"
)
func TestAccessPrivileges_String(t *testing.T) {
tests := []struct {
name string
ps ObjectPrivileges
want string
}{
{
name: "multi",
ps: ObjectPrivileges{
{Grantee: "user1", Grantor: "user1", PrivilegeType: "INSERT", IsGrantable: true},
{Grantee: "user1", Grantor: "user1", PrivilegeType: "SELECT"},
{Grantee: "user2", Grantor: "user1", PrivilegeType: "INSERT"},
{Grantee: "user2", Grantor: "user1", PrivilegeType: "SELECT", IsGrantable: true},
{Grantee: "user3", Grantor: "user1", PrivilegeType: "SELECT", IsGrantable: true},
{Grantee: "user3", Grantor: "user2", PrivilegeType: "UPDATE"},
},
want: "user1=INSERT*,SELECT/user1\n" +
"user2=INSERT,SELECT*/user1\n" +
"user3=SELECT*/user1\n" +
"user3=UPDATE/user2",
},
{
name: "one",
ps: ObjectPrivileges{
{Grantee: "user1", Grantor: "user1", PrivilegeType: "INSERT"},
},
want: "user1=INSERT/user1",
},
{
name: "empty",
ps: ObjectPrivileges{},
want: "",
},
{
name: "empty-grantor",
ps: ObjectPrivileges{
{Grantee: "user1", Grantor: "", PrivilegeType: "INSERT", IsGrantable: true},
{Grantee: "user1", Grantor: "", PrivilegeType: "SELECT"},
{Grantee: "user2", Grantor: "", PrivilegeType: "INSERT"},
{Grantee: "user2", Grantor: "", PrivilegeType: "SELECT", IsGrantable: true},
{Grantee: "user3", Grantor: "", PrivilegeType: "UPDATE"},
},
want: "user1=INSERT*,SELECT\n" +
"user2=INSERT,SELECT*\n" +
"user3=UPDATE",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.ps.String()
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("Wrong AccessPrivileges.String(): (-expected, +got):\n%s", diff)
}
})
}
}
func TestColumnPrivileges_String(t *testing.T) {
tests := []struct {
name string
ps ColumnPrivileges
want string
}{
{
name: "multi",
ps: ColumnPrivileges{
{Column: "col1", Grantee: "user1", Grantor: "user1", PrivilegeType: "INSERT", IsGrantable: true},
{Column: "col1", Grantee: "user1", Grantor: "user1", PrivilegeType: "SELECT"},
{Column: "col1", Grantee: "user2", Grantor: "user1", PrivilegeType: "INSERT"},
{Column: "col1", Grantee: "user2", Grantor: "user1", PrivilegeType: "SELECT", IsGrantable: true},
{Column: "col1", Grantee: "user3", Grantor: "user1", PrivilegeType: "SELECT", IsGrantable: true},
{Column: "col1", Grantee: "user3", Grantor: "user2", PrivilegeType: "UPDATE"},
{Column: "col2", Grantee: "user1", Grantor: "user1", PrivilegeType: "INSERT", IsGrantable: true},
{Column: "col2", Grantee: "user1", Grantor: "user1", PrivilegeType: "SELECT"},
{Column: "col2", Grantee: "user2", Grantor: "user1", PrivilegeType: "INSERT"},
{Column: "col2", Grantee: "user2", Grantor: "user1", PrivilegeType: "SELECT", IsGrantable: true},
{Column: "col2", Grantee: "user3", Grantor: "user2", PrivilegeType: "UPDATE"},
},
want: "col1:\n" +
" user1=INSERT*,SELECT/user1\n" +
" user2=INSERT,SELECT*/user1\n" +
" user3=SELECT*/user1\n" +
" user3=UPDATE/user2\n" +
"col2:\n" +
" user1=INSERT*,SELECT/user1\n" +
" user2=INSERT,SELECT*/user1\n" +
" user3=UPDATE/user2",
},
{
name: "one-multi",
ps: ColumnPrivileges{
{Column: "col2", Grantee: "user1", Grantor: "user1", PrivilegeType: "INSERT", IsGrantable: true},
{Column: "col3", Grantee: "user1", Grantor: "user1", PrivilegeType: "INSERT", IsGrantable: true},
{Column: "col3", Grantee: "user1", Grantor: "user1", PrivilegeType: "SELECT"},
{Column: "col3", Grantee: "user2", Grantor: "user1", PrivilegeType: "INSERT"},
{Column: "col3", Grantee: "user2", Grantor: "user1", PrivilegeType: "SELECT", IsGrantable: true},
{Column: "col3", Grantee: "user3", Grantor: "user2", PrivilegeType: "UPDATE"},
},
want: "col2:\n" +
" user1=INSERT*/user1\n" +
"col3:\n" +
" user1=INSERT*,SELECT/user1\n" +
" user2=INSERT,SELECT*/user1\n" +
" user3=UPDATE/user2",
},
{
name: "multi-one",
ps: ColumnPrivileges{
{Column: "col1", Grantee: "user1", Grantor: "user1", PrivilegeType: "INSERT", IsGrantable: true},
{Column: "col1", Grantee: "user1", Grantor: "user1", PrivilegeType: "SELECT"},
{Column: "col1", Grantee: "user2", Grantor: "user1", PrivilegeType: "INSERT"},
{Column: "col1", Grantee: "user2", Grantor: "user1", PrivilegeType: "SELECT", IsGrantable: true},
{Column: "col1", Grantee: "user3", Grantor: "user2", PrivilegeType: "UPDATE"},
{Column: "col2", Grantee: "user1", Grantor: "user1", PrivilegeType: "INSERT", IsGrantable: true},
},
want: "col1:\n" +
" user1=INSERT*,SELECT/user1\n" +
" user2=INSERT,SELECT*/user1\n" +
" user3=UPDATE/user2\n" +
"col2:\n" +
" user1=INSERT*/user1",
},
{
name: "one",
ps: ColumnPrivileges{
{Column: "col1", Grantee: "user1", Grantor: "user1", PrivilegeType: "INSERT"},
},
want: "col1:\n user1=INSERT/user1",
},
{
name: "empty",
ps: ColumnPrivileges{},
want: "",
},
{
name: "empty-grantor",
ps: ColumnPrivileges{
{Column: "col1", Grantee: "user1", Grantor: "", PrivilegeType: "INSERT", IsGrantable: true},
{Column: "col1", Grantee: "user1", Grantor: "", PrivilegeType: "SELECT"},
{Column: "col1", Grantee: "user2", Grantor: "", PrivilegeType: "INSERT"},
{Column: "col1", Grantee: "user2", Grantor: "", PrivilegeType: "SELECT", IsGrantable: true},
{Column: "col1", Grantee: "user3", Grantor: "", PrivilegeType: "UPDATE"},
{Column: "col2", Grantee: "user1", Grantor: "", PrivilegeType: "INSERT", IsGrantable: true},
{Column: "col2", Grantee: "user1", Grantor: "", PrivilegeType: "SELECT"},
{Column: "col2", Grantee: "user2", Grantor: "", PrivilegeType: "INSERT"},
{Column: "col2", Grantee: "user2", Grantor: "", PrivilegeType: "SELECT", IsGrantable: true},
{Column: "col2", Grantee: "user3", Grantor: "", PrivilegeType: "UPDATE"},
},
want: "col1:\n" +
" user1=INSERT*,SELECT\n" +
" user2=INSERT,SELECT*\n" +
" user3=UPDATE\n" +
"col2:\n" +
" user1=INSERT*,SELECT\n" +
" user2=INSERT,SELECT*\n" +
" user3=UPDATE",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.ps.String()
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("Wrong ColumnPrivileges.String(): (-expected, +got):\n%s", diff)
}
})
}
}
================================================
FILE: drivers/metadata/mysql/metadata.go
================================================
package mysql
import (
"time"
"github.com/gohxs/readline"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/completer"
"github.com/xo/usql/drivers/metadata"
infos "github.com/xo/usql/drivers/metadata/informationschema"
)
var (
// NewReader for MySQL databases
NewReader = infos.New(
infos.WithPlaceholder(func(int) string { return "?" }),
infos.WithSequences(false),
infos.WithCheckConstraints(false),
infos.WithCustomClauses(map[infos.ClauseName]string{
infos.ColumnsDataType: "column_type",
infos.ColumnsNumericPrecRadix: "10",
infos.FunctionColumnsNumericPrecRadix: "10",
infos.ConstraintIsDeferrable: "''",
infos.ConstraintInitiallyDeferred: "''",
infos.PrivilegesGrantor: "''",
infos.ConstraintJoinCond: "AND r.referenced_table_name = f.table_name",
}),
infos.WithSystemSchemas([]string{"mysql", "information_schema", "performance_schema", "sys"}),
infos.WithCurrentSchema("COALESCE(DATABASE(), '%')"),
infos.WithUsagePrivileges(false),
)
// NewCompleter for MySQL databases
NewCompleter = func(db drivers.DB, opts ...completer.Option) readline.AutoCompleter {
readerOpts := []metadata.ReaderOption{
// this needs to be relatively low, since autocomplete is very interactive
metadata.WithTimeout(3 * time.Second),
metadata.WithLimit(1000),
}
reader := NewReader(db, readerOpts...)
opts = append([]completer.Option{
completer.WithReader(reader),
completer.WithDB(db),
completer.WithSQLStartCommands(append(completer.CommonSqlStartCommands, "USE")),
completer.WithBeforeComplete(complete(reader)),
}, opts...)
return completer.NewDefaultCompleter(opts...)
}
)
func complete(reader metadata.Reader) completer.CompleteFunc {
return func(previousWords []string, text []rune) [][]rune {
if completer.TailMatches(completer.IGNORE_CASE, previousWords, `USE`) {
return completeWithSchemas(reader, text)
}
return nil
}
}
func completeWithSchemas(reader metadata.Reader, text []rune) [][]rune {
schemaNames := []string{}
schemas, err := reader.(metadata.SchemaReader).Schemas(metadata.Filter{WithSystem: true})
if err != nil {
return nil
}
for schemas.Next() {
schemaNames = append(schemaNames, schemas.Get().Schema)
}
return completer.CompleteFromList(text, schemaNames...)
}
================================================
FILE: drivers/metadata/oracle/metadata.go
================================================
// Package oracle provides a metadata reader
package oracle
import (
"database/sql"
"fmt"
"strings"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
)
type metaReader struct {
metadata.LoggingReader
systemSchemas string
}
var _ metadata.BasicReader = &metaReader{}
var _ metadata.IndexReader = &metaReader{}
var _ metadata.IndexColumnReader = &metaReader{}
func NewReader() func(drivers.DB, ...metadata.ReaderOption) metadata.Reader {
return func(db drivers.DB, opts ...metadata.ReaderOption) metadata.Reader {
r := &metaReader{
LoggingReader: metadata.NewLoggingReader(db, opts...),
systemSchemas: "'CTXSYS', 'FLOWS_FILES', 'MDSYS', 'OUTLN', 'SYS', 'SYSTEM', 'XDB', 'XS$NULL'",
}
return r
}
}
func (r metaReader) Catalogs(metadata.Filter) (*metadata.CatalogSet, error) {
qstr := `SELECT
UPPER(Value) AS catalog
FROM v$parameter o
WHERE name = 'db_name'
UNION ALL
SELECT
db_link AS catalog
FROM dba_db_links
ORDER BY catalog
`
rows, closeRows, err := r.Query(qstr)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewCatalogSet([]metadata.Catalog{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.Catalog{}
for rows.Next() {
rec := metadata.Catalog{}
err = rows.Scan(&rec.Catalog)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewCatalogSet(results), nil
}
func (r metaReader) Schemas(f metadata.Filter) (*metadata.SchemaSet, error) {
qstr := `SELECT
username
FROM all_users
`
conds, vals := r.conditions(f, formats{
name: "username LIKE :%d",
notSchemas: "username NOT IN (%s)",
})
if len(conds) != 0 {
qstr += " WHERE " + strings.Join(conds, " AND ")
}
qstr += `
ORDER BY username`
rows, closeRows, err := r.Query(qstr, vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewSchemaSet([]metadata.Schema{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.Schema{}
for rows.Next() {
rec := metadata.Schema{}
err = rows.Scan(&rec.Schema)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewSchemaSet(results), nil
}
// Tables from selected catalog (or all, if empty), matching schemas, names and types
func (r metaReader) Tables(f metadata.Filter) (*metadata.TableSet, error) {
qstr := `SELECT
o.owner AS table_schem,
o.object_name AS table_name,
o.object_type AS table_type
FROM all_objects o
`
conds, vals := r.conditions(f, formats{
schema: "o.owner LIKE %s",
notSchemas: "o.owner NOT IN (%s)",
name: "o.object_name LIKE :%d",
types: "o.object_type IN (%s)",
})
if len(conds) != 0 {
qstr += " WHERE " + strings.Join(conds, " AND ")
}
addSynonyms := false
for _, t := range f.Types {
if t == "SYNONYM" {
addSynonyms = true
}
}
if addSynonyms {
qstr += `
UNION ALL
SELECT
s.owner AS table_schem,
s.synonym_name AS table_name,
'SYNONYM' AS table_type
FROM all_synonyms s
`
conds, seqVals := r.conditions(f, formats{
schema: "s.owner LIKE %s",
notSchemas: "s.owner NOT IN (%s)",
name: "s.synonym_name LIKE :%d",
})
vals = append(vals, seqVals...)
if len(conds) != 0 {
qstr += " WHERE " + strings.Join(conds, " AND ")
}
}
qstr += `
ORDER BY table_schem, table_name, table_type`
rows, closeRows, err := r.Query(qstr, vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewTableSet([]metadata.Table{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.Table{}
for rows.Next() {
rec := metadata.Table{}
err = rows.Scan(&rec.Schema, &rec.Name, &rec.Type)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewTableSet(results), nil
}
func (r metaReader) Columns(f metadata.Filter) (*metadata.ColumnSet, error) {
qstr := `SELECT
c.owner,
c.table_name,
c.column_name,
c.column_id AS ordinal_position,
c.data_type,
CASE c.nullable
WHEN 'Y' THEN 'YES'
ELSE 'NO' END AS nullable,
COALESCE(c.data_length, c.data_precision, 0),
COALESCE(c.data_scale, 0),
CASE c.data_type
WHEN 'FLOAT' THEN 2
WHEN 'NUMBER' THEN 10
ELSE 0 END AS num_prec_radix,
COALESCE(c.char_col_decl_length, 0) as char_octet_length
FROM all_tab_columns c
`
conds, vals := r.conditions(f, formats{
schema: "c.owner LIKE %s",
notSchemas: "c.owner NOT IN (%s)",
parent: "c.table_name LIKE :%d",
})
if len(conds) != 0 {
qstr += " WHERE " + strings.Join(conds, " AND ")
}
qstr += `
ORDER BY c.owner, c.table_name, c.column_id`
rows, closeRows, err := r.Query(qstr, vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewColumnSet([]metadata.Column{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.Column{}
for rows.Next() {
rec := metadata.Column{}
targets := []interface{}{
&rec.Schema,
&rec.Table,
&rec.Name,
&rec.OrdinalPosition,
&rec.DataType,
&rec.IsNullable,
&rec.ColumnSize,
&rec.DecimalDigits,
&rec.NumPrecRadix,
&rec.CharOctetLength,
}
err = rows.Scan(targets...)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewColumnSet(results), nil
}
func (r metaReader) Functions(f metadata.Filter) (*metadata.FunctionSet, error) {
qstr := `SELECT
decode (b.object_type,'PACKAGE',CONCAT(CONCAT(b.object_name,'.'), a.object_name)
,b.object_name) as specific_name,
b.owner as procedure_schem,
decode (b.object_type,'PACKAGE',CONCAT(CONCAT(b.object_name,'.'), a.object_name)
,b.object_name) as procedure_name,
decode (b.object_type,'PACKAGE',decode(a.position,0,2,1,1,0),
decode(b.object_type,'PROCEDURE',1,'FUNCTION',2,0)) as procedure_type
FROM all_arguments a
JOIN all_objects b ON b.object_id = a.object_id AND a.sequence = 1
`
conds, vals := r.conditions(f, formats{
schema: "b.owner LIKE %s",
notSchemas: "b.owner NOT IN (%s)",
name: "b.object_name LIKE :%d",
types: "b.object_type IN (%s)",
})
conds = append(conds, "(b.object_type = 'PROCEDURE' OR b.object_type = 'FUNCTION' OR b.object_type = 'PACKAGE')")
qstr += " WHERE " + strings.Join(conds, " AND ")
qstr += `
ORDER BY procedure_schem, procedure_name, procedure_type`
rows, closeRows, err := r.Query(qstr, vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewFunctionSet([]metadata.Function{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.Function{}
for rows.Next() {
rec := metadata.Function{}
err = rows.Scan(
&rec.SpecificName,
&rec.Schema,
&rec.Name,
&rec.Type,
)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewFunctionSet(results), nil
}
func (r metaReader) FunctionColumns(f metadata.Filter) (*metadata.FunctionColumnSet, error) {
qstr := `SELECT
a.owner as procedure_schem,
decode (b.object_type,'PACKAGE',CONCAT(CONCAT(b.object_name,'.'),a.object_name),
b.object_name) as procedure_name,
decode(a.position,0,'RETURN_VALUE',a.argument_name) as column_name,
a.position as ordinal_position,
decode(a.position,0,5,decode(a.in_out,'IN',1,'IN/OUT',2,'OUT',4)) as column_type,
a.data_type as type_name,
COALESCE(a.data_length, a.data_precision, 0) as column_size,
COALESCE(a.data_scale, 0) as decimal_digits,
COALESCE(a.radix, 0) as num_prec_radix
FROM all_objects b
JOIN all_arguments a ON b.object_id = a.object_id AND a.data_level = 0
`
conds, vals := r.conditions(f, formats{
schema: "a.owner LIKE %s",
notSchemas: "a.owner NOT IN (%s)",
parent: "b.object_name LIKE :%d",
})
conds = append(conds, "b.object_type = 'PROCEDURE' OR b.object_type = 'FUNCTION'")
qstr += " WHERE " + strings.Join(conds, " AND ")
qstr += `
ORDER BY procedure_schem, procedure_name, ordinal_position`
rows, closeRows, err := r.Query(qstr, vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewFunctionColumnSet([]metadata.FunctionColumn{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.FunctionColumn{}
for rows.Next() {
rec := metadata.FunctionColumn{}
err = rows.Scan(
&rec.Schema,
&rec.FunctionName,
&rec.Name,
&rec.OrdinalPosition,
&rec.Type,
&rec.DataType,
&rec.ColumnSize,
&rec.DecimalDigits,
&rec.NumPrecRadix,
)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewFunctionColumnSet(results), nil
}
func (r metaReader) Indexes(f metadata.Filter) (*metadata.IndexSet, error) {
qstr := `SELECT
o.owner,
o.table_name,
o.index_name,
decode(o.uniqueness,'UNIQUE','NO','YES')
FROM all_indexes o
`
conds, vals := r.conditions(f, formats{
schema: "o.owner LIKE %s",
notSchemas: "o.owner NOT IN (%s)",
parent: "o.table_name LIKE :%d",
name: "o.index_name LIKE :%d",
})
if len(conds) != 0 {
qstr += " WHERE " + strings.Join(conds, " AND ")
}
qstr += `
ORDER BY o.owner, o.table_name, o.index_name`
rows, closeRows, err := r.Query(qstr, vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewIndexSet([]metadata.Index{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.Index{}
for rows.Next() {
rec := metadata.Index{}
err = rows.Scan(&rec.Schema, &rec.Table, &rec.Name, &rec.IsUnique)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewIndexSet(results), nil
}
func (r metaReader) IndexColumns(f metadata.Filter) (*metadata.IndexColumnSet, error) {
qstr := `SELECT
o.owner,
o.table_name,
o.index_name,
b.column_name,
b.column_position
FROM all_indexes o
JOIN all_ind_columns b ON o.owner = b.index_owner AND o.index_name = b.index_name
`
conds, vals := r.conditions(f, formats{
schema: "o.owner LIKE %s",
notSchemas: "o.owner NOT IN (%s)",
parent: "o.table_name LIKE :%d",
name: "o.index_name LIKE :%d",
})
if len(conds) != 0 {
qstr += " WHERE " + strings.Join(conds, " AND ")
}
qstr += `
ORDER BY o.owner, o.table_name, o.index_name, b.column_position`
rows, closeRows, err := r.Query(qstr, vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewIndexColumnSet([]metadata.IndexColumn{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.IndexColumn{}
for rows.Next() {
rec := metadata.IndexColumn{}
err = rows.Scan(&rec.Schema, &rec.Table, &rec.IndexName, &rec.Name, &rec.OrdinalPosition)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewIndexColumnSet(results), nil
}
func (r metaReader) conditions(filter metadata.Filter, formats formats) ([]string, []interface{}) {
baseParam := 1
conds := []string{}
vals := []interface{}{}
if filter.Schema != "" && formats.schema != "" {
vals = append(vals, strings.ToUpper(filter.Schema))
conds = append(conds, fmt.Sprintf(formats.schema, fmt.Sprintf(":%d", baseParam)))
baseParam++
}
if !filter.WithSystem && formats.notSchemas != "" {
conds = append(conds, fmt.Sprintf(formats.notSchemas, r.systemSchemas))
}
if filter.OnlyVisible && formats.schema != "" {
conds = append(conds, fmt.Sprintf(formats.schema, "user"))
}
if filter.Parent != "" && formats.parent != "" {
vals = append(vals, strings.ToUpper(filter.Parent))
conds = append(conds, fmt.Sprintf(formats.parent, baseParam))
baseParam++
}
if filter.Name != "" && formats.name != "" {
vals = append(vals, strings.ToUpper(filter.Name))
conds = append(conds, fmt.Sprintf(formats.name, baseParam))
baseParam++
}
if len(filter.Types) != 0 && formats.types != "" {
pholders := []string{}
for _, t := range filter.Types {
vals = append(vals, strings.ToUpper(t))
pholders = append(pholders, fmt.Sprintf(":%d", baseParam))
baseParam++
}
if len(pholders) != 0 {
conds = append(conds, fmt.Sprintf(formats.types, strings.Join(pholders, ", ")))
}
}
return conds, vals
}
type formats struct {
schema string
notSchemas string
parent string
name string
types string
}
================================================
FILE: drivers/metadata/postgres/metadata.go
================================================
// Package postgres provides a metadata reader
package postgres
import (
"database/sql"
"fmt"
"strings"
"github.com/lib/pq"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
infos "github.com/xo/usql/drivers/metadata/informationschema"
)
type metaReader struct {
metadata.LoggingReader
limit int
}
var _ metadata.CatalogReader = &metaReader{}
var _ metadata.TableReader = &metaReader{}
var _ metadata.ColumnStatReader = &metaReader{}
var _ metadata.IndexReader = &metaReader{}
var _ metadata.IndexColumnReader = &metaReader{}
var _ metadata.TriggerReader = &metaReader{}
func NewReader() func(drivers.DB, ...metadata.ReaderOption) metadata.Reader {
return func(db drivers.DB, opts ...metadata.ReaderOption) metadata.Reader {
newIS := infos.New(
infos.WithIndexes(false),
infos.WithCustomClauses(map[infos.ClauseName]string{
infos.ColumnsColumnSize: "COALESCE(character_maximum_length, numeric_precision, datetime_precision, interval_precision, 0)",
infos.FunctionColumnsColumnSize: "COALESCE(character_maximum_length, numeric_precision, datetime_precision, interval_precision, 0)",
}),
infos.WithSystemSchemas([]string{"pg_catalog", "pg_toast", "information_schema"}),
infos.WithCurrentSchema("CURRENT_SCHEMA"),
infos.WithDataTypeFormatter(dataTypeFormatter))
return metadata.NewPluginReader(
newIS(db, opts...),
&metaReader{
LoggingReader: metadata.NewLoggingReader(db, opts...),
},
)
}
}
func dataTypeFormatter(col metadata.Column) string {
switch col.DataType {
case "bit", "character":
return fmt.Sprintf("%s(%d)", col.DataType, col.ColumnSize)
case "bit varying", "character varying":
if col.ColumnSize != 0 {
return fmt.Sprintf("%s(%d)", col.DataType, col.ColumnSize)
} else {
return col.DataType
}
case "numeric":
if col.ColumnSize != 0 {
return fmt.Sprintf("numeric(%d,%d)", col.ColumnSize, col.DecimalDigits)
} else {
return col.DataType
}
case "time without time zone":
return fmt.Sprintf("time(%d) without time zone", col.ColumnSize)
case "time with time zone":
return fmt.Sprintf("time(%d) with time zone", col.ColumnSize)
case "timestamp without time zone":
return fmt.Sprintf("timestamp(%d) without time zone", col.ColumnSize)
case "timestamp with time zone":
return fmt.Sprintf("timestamp(%d) with time zone", col.ColumnSize)
default:
return col.DataType
}
}
func (r *metaReader) SetLimit(l int) {
r.limit = l
}
type Catalog struct {
metadata.Catalog
Owner string
Encoding string
Collate string
Ctype string
AccessPrivileges string
}
func (s Catalog) Values() []interface{} {
return []interface{}{s.Catalog.Catalog, s.Owner, s.Encoding, s.Collate, s.Ctype, s.AccessPrivileges}
}
func (s Catalog) GetCatalog() metadata.Catalog {
return s.Catalog
}
var (
catalogsColumnName = []string{"Catalog", "Owner", "Encoding", "Collate", "Ctype", "Access privileges"}
)
func (r metaReader) Catalogs(metadata.Filter) (*metadata.CatalogSet, error) {
qstr := `SELECT d.datname as "Name",
pg_catalog.pg_get_userbyid(d.datdba) as "Owner",
pg_catalog.pg_encoding_to_char(d.encoding) as "Encoding",
d.datcollate as "Collate",
d.datctype as "Ctype",
COALESCE(pg_catalog.array_to_string(d.datacl, E'\n'),'') AS "Access privileges"
FROM pg_catalog.pg_database d`
rows, closeRows, err := r.query(qstr, []string{}, "1")
if err != nil {
return nil, err
}
defer closeRows()
var results []metadata.Result
for rows.Next() {
rec := Catalog{
Catalog: metadata.Catalog{},
}
err = rows.Scan(&rec.Catalog.Catalog, &rec.Owner, &rec.Encoding, &rec.Collate, &rec.Ctype, &rec.AccessPrivileges)
if err != nil {
return nil, err
}
results = append(results, &rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewCatalogSetWithColumns(results, catalogsColumnName), nil
}
func (r metaReader) Tables(f metadata.Filter) (*metadata.TableSet, error) {
qstr := `SELECT n.nspname as "Schema",
c.relname as "Name",
CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' ELSE 'unknown' END as "Type",
COALESCE((c.reltuples / NULLIF(c.relpages, 0)) * (pg_catalog.pg_relation_size(c.oid) / current_setting('block_size')::int), 0)::bigint as "Rows",
pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) as "Size",
COALESCE(pg_catalog.obj_description(c.oid, 'pg_class'), '') as "Description"
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
`
conds := []string{"n.nspname !~ '^pg_toast' AND c.relkind != 'c'"}
vals := []interface{}{}
if f.OnlyVisible {
conds = append(conds, "pg_catalog.pg_table_is_visible(c.oid)")
}
if !f.WithSystem {
conds = append(conds, "n.nspname NOT IN ('pg_catalog', 'information_schema')")
}
if f.Schema != "" {
vals = append(vals, f.Schema)
conds = append(conds, fmt.Sprintf("n.nspname LIKE $%d", len(vals)))
}
if f.Name != "" {
vals = append(vals, f.Name)
conds = append(conds, fmt.Sprintf("c.relname LIKE $%d", len(vals)))
}
if len(f.Types) != 0 {
tableTypes := map[string][]rune{
"TABLE": {'r', 'p', 's', 'f'},
"VIEW": {'v'},
"MATERIALIZED VIEW": {'m'},
"SEQUENCE": {'S'},
}
pholders := []string{"''"}
for _, t := range f.Types {
for _, k := range tableTypes[t] {
vals = append(vals, string(k))
pholders = append(pholders, fmt.Sprintf("$%d", len(vals)))
}
}
conds = append(conds, fmt.Sprintf("c.relkind IN (%s)", strings.Join(pholders, ", ")))
}
rows, closeRows, err := r.query(qstr, conds, "1, 3, 2", vals...)
if err != nil {
if err == sql.ErrNoRows {
return metadata.NewTableSet([]metadata.Table{}), nil
}
return nil, err
}
defer closeRows()
results := []metadata.Table{}
for rows.Next() {
rec := metadata.Table{}
err = rows.Scan(&rec.Schema, &rec.Name, &rec.Type, &rec.Rows, &rec.Size, &rec.Comment)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewTableSet(results), nil
}
func (r metaReader) ColumnStats(f metadata.Filter) (*metadata.ColumnStatSet, error) {
tables, err := r.Tables(metadata.Filter{Schema: f.Schema, Name: f.Parent, WithSystem: true})
if err != nil {
return nil, err
}
rowNum := int64(0)
if tables.Next() {
rowNum = tables.Get().Rows
}
qstr := `
SELECT
n.nspname,
c.relname,
a.attname,
COALESCE(s.avg_width, 0),
COALESCE(s.null_frac, 0.0),
COALESCE(CASE WHEN n_distinct >= 0 THEN n_distinct ELSE (-n_distinct * $1) END::bigint, 0) AS n_distinct,
COALESCE((histogram_bounds::text::text[])[1], ''),
COALESCE((histogram_bounds::text::text[])[array_length(histogram_bounds::text::text[], 1)], ''),
most_common_vals::text::text[],
most_common_freqs::text::text[]
FROM pg_catalog.pg_namespace n
JOIN pg_catalog.pg_class c ON c.relnamespace = n.oid
JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid AND a.attnum > 0
LEFT JOIN pg_catalog.pg_stats s ON n.nspname = s.schemaname AND c.relname = s.tablename AND a.attname = s.attname
`
conds := []string{}
vals := []interface{}{rowNum}
if f.Schema != "" {
vals = append(vals, f.Schema)
conds = append(conds, fmt.Sprintf("n.nspname LIKE $%d", len(vals)))
}
if f.Parent != "" {
vals = append(vals, f.Parent)
conds = append(conds, fmt.Sprintf("c.relname LIKE $%d", len(vals)))
}
if f.Name != "" {
vals = append(vals, f.Name)
conds = append(conds, fmt.Sprintf("a.attname LIKE $%d", len(vals)))
}
rows, closeRows, err := r.query(qstr, conds, "a.attnum", vals...)
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.ColumnStat{}
for rows.Next() {
rec := metadata.ColumnStat{}
err = rows.Scan(
&rec.Schema,
&rec.Table,
&rec.Name,
&rec.AvgWidth,
&rec.NullFrac,
&rec.NumDistinct,
&rec.Min,
&rec.Max,
pq.Array(&rec.TopN),
pq.Array(&rec.TopNFreqs),
)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewColumnStatSet(results), nil
}
func (r metaReader) Indexes(f metadata.Filter) (*metadata.IndexSet, error) {
qstr := `
SELECT
'postgres' as "Catalog",
n.nspname as "Schema",
c2.relname as "Table",
c.relname as "Name",
CASE i.indisprimary WHEN TRUE THEN 'YES' ELSE 'NO' END,
CASE i.indisunique WHEN TRUE THEN 'YES' ELSE 'NO' END,
COALESCE(am.amname,
CASE c.relkind
WHEN 'i' THEN 'index'
WHEN 'I' THEN 'partitioned index'
END
) as "Type"
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid
LEFT JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid
LEFT JOIN pg_am am ON am.oid=c.relam`
conds := []string{
"c.relkind IN ('i','I','')",
"n.nspname !~ '^pg_toast'",
}
if f.OnlyVisible {
conds = append(conds, "pg_catalog.pg_table_is_visible(c.oid)")
}
vals := []interface{}{}
if !f.WithSystem {
conds = append(conds, "n.nspname NOT IN ('pg_catalog', 'information_schema')")
}
if f.Schema != "" {
vals = append(vals, f.Schema)
conds = append(conds, fmt.Sprintf("n.nspname LIKE $%d", len(vals)))
}
if f.Parent != "" {
vals = append(vals, f.Parent)
conds = append(conds, fmt.Sprintf("c2.relname LIKE $%d", len(vals)))
}
if f.Name != "" {
vals = append(vals, f.Name)
conds = append(conds, fmt.Sprintf("c.relname LIKE $%d", len(vals)))
}
rows, closeRows, err := r.query(qstr, conds, "1, 2, 4", vals...)
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.Index{}
for rows.Next() {
rec := metadata.Index{}
err = rows.Scan(&rec.Catalog, &rec.Schema, &rec.Table, &rec.Name, &rec.IsUnique, &rec.IsPrimary, &rec.Type)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewIndexSet(results), nil
}
func (r metaReader) IndexColumns(f metadata.Filter) (*metadata.IndexColumnSet, error) {
qstr := `
SELECT
'postgres' as "Catalog",
n.nspname as "Schema",
c2.relname as "Table",
c.relname as "IndexName",
a.attname AS "Name",
pg_catalog.format_type(a.atttypid, a.atttypmod) AS "DataType",
a.attnum AS "OrdinalPosition"
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid
JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid
JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid
`
conds := []string{
"c.relkind IN ('i','I','')",
"n.nspname <> 'pg_catalog'",
"n.nspname <> 'information_schema'",
"n.nspname !~ '^pg_toast'",
"a.attnum > 0",
"NOT a.attisdropped",
}
if f.OnlyVisible {
conds = append(conds, "pg_catalog.pg_table_is_visible(c.oid)")
}
vals := []interface{}{}
if !f.WithSystem {
conds = append(conds, "n.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema')")
}
if f.Schema != "" {
vals = append(vals, f.Schema)
conds = append(conds, fmt.Sprintf("n.nspname LIKE $%d", len(vals)))
}
if f.Parent != "" {
vals = append(vals, f.Parent)
conds = append(conds, fmt.Sprintf("c2.relname LIKE $%d", len(vals)))
}
if f.Name != "" {
vals = append(vals, f.Name)
conds = append(conds, fmt.Sprintf("c.relname LIKE $%d", len(vals)))
}
rows, closeRows, err := r.query(qstr, conds, "1, 2, 3, 4, 7", vals...)
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.IndexColumn{}
for rows.Next() {
rec := metadata.IndexColumn{}
err = rows.Scan(&rec.Catalog, &rec.Schema, &rec.Table, &rec.IndexName, &rec.Name, &rec.DataType, &rec.OrdinalPosition)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewIndexColumnSet(results), nil
}
func (r metaReader) Triggers(f metadata.Filter) (*metadata.TriggerSet, error) {
qstr := `SELECT
n.nspname,
c.relname,
t.tgname,
pg_catalog.pg_get_triggerdef(t.oid, true)
FROM
pg_catalog.pg_trigger t
JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace`
conds := []string{`(
NOT t.tgisinternal OR (t.tgisinternal AND t.tgenabled = 'D')
OR
EXISTS (SELECT 1 FROM pg_catalog.pg_depend WHERE objid = t.oid
AND
refclassid = 'pg_catalog.pg_trigger'::pg_catalog.regclass)
)`}
vals := []interface{}{}
if f.Schema != "" {
vals = append(vals, f.Schema)
conds = append(conds, fmt.Sprintf("n.nspname LIKE $%d", len(vals)))
}
if f.Parent != "" {
vals = append(vals, f.Parent)
conds = append(conds, fmt.Sprintf("c.relname LIKE $%d", len(vals)))
}
if f.Name != "" {
vals = append(vals, f.Name)
conds = append(conds, fmt.Sprintf("t.tgname LIKE $%d", len(vals)))
}
rows, closeRows, err := r.query(qstr, conds, "t.tgname", vals...)
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.Trigger{}
for rows.Next() {
rec := metadata.Trigger{}
err = rows.Scan(
&rec.Schema,
&rec.Table,
&rec.Name,
&rec.Definition,
)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewTriggerSet(results), nil
}
func (r metaReader) query(qstr string, conds []string, order string, vals ...interface{}) (*sql.Rows, func(), error) {
if len(conds) != 0 {
qstr += "\nWHERE " + strings.Join(conds, " AND ")
}
if order != "" {
qstr += "\nORDER BY " + order
}
if r.limit != 0 {
qstr += fmt.Sprintf("\nLIMIT %d", r.limit)
}
return r.Query(qstr, vals...)
}
================================================
FILE: drivers/metadata/postgres/metadata_test.go
================================================
package postgres_test
import (
"database/sql"
"flag"
"fmt"
"log"
"os"
"strings"
"testing"
dt "github.com/ory/dockertest/v3"
dc "github.com/ory/dockertest/v3/docker"
"github.com/xo/usql/drivers/metadata"
"github.com/xo/usql/drivers/metadata/postgres"
_ "github.com/xo/usql/drivers/postgres"
)
type Database struct {
BuildArgs []dc.BuildArg
RunOptions *dt.RunOptions
Exec []string
Driver string
URL string
DockerPort string
Resource *dt.Resource
DB *sql.DB
Opts []metadata.ReaderOption
Reader metadata.BasicReader
}
var dbName string = "postgres"
var db = Database{
BuildArgs: []dc.BuildArg{
{Name: "BASE_IMAGE", Value: "postgres:13"},
{Name: "SCHEMA_URL", Value: "https://raw.githubusercontent.com/jOOQ/sakila/main/postgres-sakila-db/postgres-sakila-schema.sql"},
{Name: "TARGET", Value: "/docker-entrypoint-initdb.d"},
{Name: "USER", Value: "root"},
},
RunOptions: &dt.RunOptions{
Name: "usql-pgsql",
Cmd: []string{"-c", "log_statement=all", "-c", "log_min_duration_statement=0"},
Env: []string{"POSTGRES_PASSWORD=pw"},
},
Driver: "postgres",
URL: "postgres://postgres:pw@localhost:%s/postgres?sslmode=disable",
DockerPort: "5432/tcp",
}
func TestMain(m *testing.M) {
cleanup := true
flag.BoolVar(&cleanup, "cleanup", true, "delete containers when finished")
flag.Parse()
pool, err := dt.NewPool("")
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
var ok bool
db.Resource, ok = pool.ContainerByName(db.RunOptions.Name)
if !ok {
buildOpts := &dt.BuildOptions{
ContextDir: "../../testdata/docker",
BuildArgs: db.BuildArgs,
}
db.Resource, err = pool.BuildAndRunWithBuildOptions(buildOpts, db.RunOptions)
if err != nil {
log.Fatal("Could not start resource: ", err)
}
}
// exponential backoff-retry, because the application in the container might not be ready to accept connections yet
if err := pool.Retry(func() error {
hostPort := db.Resource.GetPort(db.DockerPort)
var err error
db.DB, err = sql.Open(db.Driver, fmt.Sprintf(db.URL, hostPort))
if err != nil {
return err
}
return db.DB.Ping()
}); err != nil {
log.Fatal("Timed out waiting for db: ", err)
}
db.Reader = postgres.NewReader()(db.DB).(metadata.BasicReader)
if len(db.Exec) != 0 {
exitCode, err := db.Resource.Exec(db.Exec, dt.ExecOptions{
StdIn: os.Stdin,
StdOut: os.Stdout,
StdErr: os.Stderr,
TTY: true,
})
if err != nil || exitCode != 0 {
log.Fatal("Could not load schema: ", err)
}
}
code := m.Run()
// You can't defer this because os.Exit doesn't care for defer
if cleanup {
if err := pool.Purge(db.Resource); err != nil {
log.Fatal("Could not purge resource: ", err)
}
}
os.Exit(code)
}
func TestTriggers(t *testing.T) {
schema := "public"
expected := "film_fulltext_trigger, last_updated"
parent := "film"
r := postgres.NewReader()(db.DB).(metadata.TriggerReader)
result, err := r.Triggers(metadata.Filter{Schema: schema, Parent: parent})
if err != nil {
log.Fatalf("Could not read %s triggers: %v", dbName, err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Name)
}
actual := strings.Join(names, ", ")
if actual != expected {
t.Errorf("Wrong %s trigger names, expected:\n %v\ngot:\n %v", dbName, expected, names)
}
}
func TestColumns(t *testing.T) {
// Only testing postgres specific datatype formatting.
// The rest of the functionality is covered by informationschema/metadata_test.go:TestColumns
type test struct {
typeDef string
want string
}
schema := "public"
table := "test_dtypes"
tests := []test{
{typeDef: "bit", want: "bit(1)"},
{typeDef: "bit(1)", want: "bit(1)"},
{typeDef: "bit varying", want: "bit varying"},
{typeDef: "bit varying(2)", want: "bit varying(2)"},
{typeDef: "character", want: "character(1)"},
{typeDef: "character(3)", want: "character(3)"},
{typeDef: "character varying", want: "character varying"},
{typeDef: "character varying(4)", want: "character varying(4)"},
{typeDef: "numeric", want: "numeric"},
{typeDef: "numeric(1,0)", want: "numeric(1,0)"},
{typeDef: "time", want: "time(6) without time zone"},
{typeDef: "time(4)", want: "time(4) without time zone"},
{typeDef: "time(6)", want: "time(6) without time zone"},
{typeDef: "time with time zone", want: "time(6) with time zone"},
{typeDef: "time(3) with time zone", want: "time(3) with time zone"},
{typeDef: "timestamp", want: "timestamp(6) without time zone"},
{typeDef: "timestamp(2)", want: "timestamp(2) without time zone"},
{typeDef: "timestamp with time zone", want: "timestamp(6) with time zone"},
{typeDef: "timestamp(1) with time zone", want: "timestamp(1) with time zone"},
{typeDef: "bigint", want: "bigint"},
{typeDef: "bigserial", want: "bigint"},
{typeDef: "boolean", want: "boolean"},
{typeDef: "box", want: "box"},
{typeDef: "bytea", want: "bytea"},
{typeDef: "cidr", want: "cidr"},
{typeDef: "circle", want: "circle"},
{typeDef: "date", want: "date"},
{typeDef: "double precision", want: "double precision"},
{typeDef: "inet", want: "inet"},
{typeDef: "integer", want: "integer"},
{typeDef: "json", want: "json"},
{typeDef: "jsonb", want: "jsonb"},
{typeDef: "line", want: "line"},
{typeDef: "lseg", want: "lseg"},
{typeDef: "macaddr", want: "macaddr"},
{typeDef: "macaddr8", want: "macaddr8"},
{typeDef: "money", want: "money"},
{typeDef: "path", want: "path"},
{typeDef: "pg_lsn", want: "pg_lsn"},
{typeDef: "pg_snapshot", want: "pg_snapshot"},
{typeDef: "point", want: "point"},
{typeDef: "polygon", want: "polygon"},
{typeDef: "real", want: "real"},
{typeDef: "smallint", want: "smallint"},
{typeDef: "smallserial", want: "smallint"},
{typeDef: "serial", want: "integer"},
{typeDef: "text", want: "text"},
{typeDef: "tsvector", want: "tsvector"},
{typeDef: "txid_snapshot", want: "txid_snapshot"},
{typeDef: "uuid", want: "uuid"},
{typeDef: "xml", want: "xml"},
}
// Create table
colExpressions := []string{}
for i, test := range tests {
colExpressions = append(colExpressions, fmt.Sprintf("column_%d %s", i, test.typeDef))
}
query := fmt.Sprintf("CREATE TABLE %s.%s (%s)", schema, table, strings.Join(colExpressions, ", "))
db.DB.Exec(query)
defer db.DB.Exec(fmt.Sprintf("DROP TABLE %s.%s", schema, table))
// Read data types
r := postgres.NewReader()(db.DB).(metadata.ColumnReader)
result, err := r.Columns(metadata.Filter{Schema: schema, Parent: table})
if err != nil {
log.Fatalf("Could not read %s columns: %v", dbName, err)
}
actualTypes := []string{}
for result.Next() {
actualTypes = append(actualTypes, result.Get().DataType)
}
// Compare
for i, test := range tests {
if actualTypes[i] != test.want {
t.Errorf("Wrong %s column data type, expected:\n %s, got:\n %s", dbName, test.want, actualTypes[i])
}
}
}
func TestIndexes(t *testing.T) {
schema := "public"
table := "tmp_table"
tests := []struct {
indexType string
want string
}{
{
indexType: "btree",
want: "btree",
},
{
indexType: "hash",
want: "hash",
},
}
columns := []string{}
for _, v := range tests {
columns = append(columns, fmt.Sprintf("column_%s integer", v.indexType))
}
indexes := []string{}
for _, v := range tests {
indexes = append(indexes, fmt.Sprintf("CREATE INDEX %s_index ON %s.%s USING %[1]s (column_%[1]s)", v.indexType, schema, table))
}
query := `
CREATE TABLE %s.%s (%s);
-- Indexes creation sql
%s
`
db.DB.Exec(fmt.Sprintf(query, schema, table, strings.Join(columns, ", "), strings.Join(indexes, ";")))
defer db.DB.Exec(fmt.Sprintf("DROP TABLE %s.%s", schema, table))
r := postgres.NewReader()(db.DB).(metadata.IndexReader)
t.Run("Get info about access method for specyfic index.", func(t *testing.T) {
accessMethods := []string{}
for _, v := range tests {
result, err := r.Indexes(metadata.Filter{Name: fmt.Sprintf("%s_index", v.indexType)})
if err != nil {
log.Fatalf("Could not get Index information: %s", err)
}
for result.Next() {
accessMethods = append(accessMethods, result.Get().Type)
}
}
for i, test := range tests {
if accessMethods[i] != test.want {
t.Errorf("Wrong %s index access method, expected:\n %s, got:\n %s", dbName, test.want, accessMethods[i])
}
}
})
t.Run("Get info about index access method for all table indexes.", func(t *testing.T) {
result, err := r.Indexes(metadata.Filter{Schema: schema, Parent: table})
if err != nil {
log.Fatalf("Could not get Index information: %s", err)
}
accessMethods := []string{}
for result.Next() {
accessMethods = append(accessMethods, result.Get().Type)
}
for i, test := range tests {
if accessMethods[i] != test.want {
t.Errorf("Wrong %s index access method, expected:\n %s, got:\n %s", dbName, test.want, accessMethods[i])
}
}
})
}
================================================
FILE: drivers/metadata/reader.go
================================================
package metadata
import (
"context"
"database/sql"
"time"
"github.com/xo/usql/text"
)
// PluginReader allows to be easily composed from other readers
type PluginReader struct {
catalogs func(Filter) (*CatalogSet, error)
schemas func(Filter) (*SchemaSet, error)
tables func(Filter) (*TableSet, error)
columns func(Filter) (*ColumnSet, error)
columnStats func(Filter) (*ColumnStatSet, error)
indexes func(Filter) (*IndexSet, error)
indexColumns func(Filter) (*IndexColumnSet, error)
triggers func(Filter) (*TriggerSet, error)
constraints func(Filter) (*ConstraintSet, error)
constraintColumns func(Filter) (*ConstraintColumnSet, error)
functions func(Filter) (*FunctionSet, error)
functionColumns func(Filter) (*FunctionColumnSet, error)
sequences func(Filter) (*SequenceSet, error)
privilegeSummaries func(Filter) (*PrivilegeSummarySet, error)
}
var _ ExtendedReader = &PluginReader{}
// NewPluginReader allows to be easily composed from other readers
func NewPluginReader(readers ...Reader) Reader {
p := PluginReader{}
for _, i := range readers {
if r, ok := i.(CatalogReader); ok {
p.catalogs = r.Catalogs
}
if r, ok := i.(SchemaReader); ok {
p.schemas = r.Schemas
}
if r, ok := i.(TableReader); ok {
p.tables = r.Tables
}
if r, ok := i.(ColumnReader); ok {
p.columns = r.Columns
}
if r, ok := i.(ColumnStatReader); ok {
p.columnStats = r.ColumnStats
}
if r, ok := i.(IndexReader); ok {
p.indexes = r.Indexes
}
if r, ok := i.(IndexColumnReader); ok {
p.indexColumns = r.IndexColumns
}
if r, ok := i.(TriggerReader); ok {
p.triggers = r.Triggers
}
if r, ok := i.(ConstraintReader); ok {
p.constraints = r.Constraints
}
if r, ok := i.(ConstraintColumnReader); ok {
p.constraintColumns = r.ConstraintColumns
}
if r, ok := i.(FunctionReader); ok {
p.functions = r.Functions
}
if r, ok := i.(FunctionColumnReader); ok {
p.functionColumns = r.FunctionColumns
}
if r, ok := i.(SequenceReader); ok {
p.sequences = r.Sequences
}
if r, ok := i.(PrivilegeSummaryReader); ok {
p.privilegeSummaries = r.PrivilegeSummaries
}
}
return &p
}
func (p PluginReader) Catalogs(f Filter) (*CatalogSet, error) {
if p.catalogs == nil {
return nil, text.ErrNotSupported
}
return p.catalogs(f)
}
func (p PluginReader) Schemas(f Filter) (*SchemaSet, error) {
if p.schemas == nil {
return nil, text.ErrNotSupported
}
return p.schemas(f)
}
func (p PluginReader) Tables(f Filter) (*TableSet, error) {
if p.tables == nil {
return nil, text.ErrNotSupported
}
return p.tables(f)
}
func (p PluginReader) Columns(f Filter) (*ColumnSet, error) {
if p.columns == nil {
return nil, text.ErrNotSupported
}
return p.columns(f)
}
func (p PluginReader) ColumnStats(f Filter) (*ColumnStatSet, error) {
if p.columnStats == nil {
return nil, text.ErrNotSupported
}
return p.columnStats(f)
}
func (p PluginReader) Indexes(f Filter) (*IndexSet, error) {
if p.indexes == nil {
return nil, text.ErrNotSupported
}
return p.indexes(f)
}
func (p PluginReader) IndexColumns(f Filter) (*IndexColumnSet, error) {
if p.indexColumns == nil {
return nil, text.ErrNotSupported
}
return p.indexColumns(f)
}
func (p PluginReader) Triggers(f Filter) (*TriggerSet, error) {
if p.triggers == nil {
return nil, text.ErrNotSupported
}
return p.triggers(f)
}
func (p PluginReader) Constraints(f Filter) (*ConstraintSet, error) {
if p.constraints == nil {
return nil, text.ErrNotSupported
}
return p.constraints(f)
}
func (p PluginReader) ConstraintColumns(f Filter) (*ConstraintColumnSet, error) {
if p.constraintColumns == nil {
return nil, text.ErrNotSupported
}
return p.constraintColumns(f)
}
func (p PluginReader) Functions(f Filter) (*FunctionSet, error) {
if p.functions == nil {
return nil, text.ErrNotSupported
}
return p.functions(f)
}
func (p PluginReader) FunctionColumns(f Filter) (*FunctionColumnSet, error) {
if p.functionColumns == nil {
return nil, text.ErrNotSupported
}
return p.functionColumns(f)
}
func (p PluginReader) Sequences(f Filter) (*SequenceSet, error) {
if p.sequences == nil {
return nil, text.ErrNotSupported
}
return p.sequences(f)
}
func (p PluginReader) PrivilegeSummaries(f Filter) (*PrivilegeSummarySet, error) {
if p.privilegeSummaries == nil {
return nil, text.ErrNotSupported
}
return p.privilegeSummaries(f)
}
type LoggingReader struct {
db DB
logger logger
dryRun bool
timeout time.Duration
}
type logger interface {
Println(...interface{})
}
func NewLoggingReader(db DB, opts ...ReaderOption) LoggingReader {
r := LoggingReader{
db: db,
}
for _, o := range opts {
o(&r)
}
return r
}
// ReaderOption to configure the reader
type ReaderOption func(Reader)
// WithLogger used to log queries before executing them
func WithLogger(l logger) ReaderOption {
return func(r Reader) {
r.(loggerSetter).setLogger(l)
}
}
// WithDryRun allows to avoid running any queries
func WithDryRun(d bool) ReaderOption {
return func(r Reader) {
r.(loggerSetter).setDryRun(d)
}
}
// WithTimeout for a single query
func WithTimeout(t time.Duration) ReaderOption {
return func(r Reader) {
r.(loggerSetter).setTimeout(t)
}
}
// WithLimit for a single query, if the reader supports it
func WithLimit(l int) ReaderOption {
return func(r Reader) {
if rl, ok := r.(limiter); ok {
rl.SetLimit(l)
}
}
}
type loggerSetter interface {
setLogger(logger)
setDryRun(bool)
setTimeout(t time.Duration)
}
type limiter interface {
SetLimit(l int)
}
func (r *LoggingReader) setLogger(l logger) {
r.logger = l
}
func (r *LoggingReader) setDryRun(d bool) {
r.dryRun = d
}
func (r *LoggingReader) setTimeout(t time.Duration) {
r.timeout = t
}
func (r LoggingReader) Query(q string, v ...interface{}) (*sql.Rows, CloseFunc, error) {
if r.logger != nil {
r.logger.Println(q)
r.logger.Println(v)
}
if r.dryRun {
return nil, nil, sql.ErrNoRows
}
if r.timeout != 0 {
ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
rows, err := r.db.QueryContext(ctx, q, v...)
return rows, func() { cancel(); rows.Close() }, err
}
rows, err := r.db.Query(q, v...)
return rows, func() { rows.Close() }, err
}
// CloseFunc should be called when result won't be processed anymore
type CloseFunc func()
================================================
FILE: drivers/metadata/writer.go
================================================
package metadata
import (
"context"
"database/sql"
"fmt"
"io"
"strings"
"github.com/xo/dburl"
"github.com/xo/tblfmt"
"github.com/xo/usql/env"
"github.com/xo/usql/text"
)
// DB is the common interface for database operations, compatible with
// database/sql.DB and database/sql.Tx.
type DB interface {
Exec(string, ...interface{}) (sql.Result, error)
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
Query(string, ...interface{}) (*sql.Rows, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRow(string, ...interface{}) *sql.Row
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
Prepare(string) (*sql.Stmt, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
}
// DefaultWriter using an existing db introspector
type DefaultWriter struct {
r Reader
db DB
w io.Writer
tableTypes map[rune][]string
funcTypes map[rune][]string
systemSchemas map[string]struct{}
// custom functions for easier overloading
listAllDbs func(string, bool) error
}
func NewDefaultWriter(r Reader, opts ...WriterOption) func(db DB, w io.Writer) Writer {
defaultWriter := &DefaultWriter{
r: r,
tableTypes: map[rune][]string{
't': {"TABLE", "BASE TABLE", "SYSTEM TABLE", "SYNONYM", "LOCAL TEMPORARY", "GLOBAL TEMPORARY"},
'v': {"VIEW", "SYSTEM VIEW"},
'm': {"MATERIALIZED VIEW"},
's': {"SEQUENCE"},
},
funcTypes: map[rune][]string{
'a': {"AGGREGATE"},
'n': {"FUNCTION"},
'p': {"PROCEDURE", "PACKAGE"},
't': {"TRIGGER"},
'w': {"WINDOW"},
},
systemSchemas: map[string]struct{}{
"information_schema": {},
},
}
for _, o := range opts {
o(defaultWriter)
}
return func(db DB, w io.Writer) Writer {
defaultWriter.db = db
defaultWriter.w = w
return defaultWriter
}
}
// WriterOption to configure the DefaultWriter
type WriterOption func(*DefaultWriter)
// WithSystemSchemas that are ignored unless showSystem is true
func WithSystemSchemas(schemas []string) WriterOption {
return func(w *DefaultWriter) {
w.systemSchemas = make(map[string]struct{}, len(schemas))
for _, s := range schemas {
w.systemSchemas[s] = struct{}{}
}
}
}
// WithListAllDbs that lists all catalogs
func WithListAllDbs(f func(string, bool) error) WriterOption {
return func(w *DefaultWriter) {
w.listAllDbs = f
}
}
// DescribeFunctions matching pattern
func (w DefaultWriter) DescribeFunctions(u *dburl.URL, funcTypes, pattern string, verbose, showSystem bool) error {
r, ok := w.r.(FunctionReader)
if !ok {
return fmt.Errorf(text.NotSupportedByDriver, `\df`, u.Driver)
}
types := []string{}
for k, v := range w.funcTypes {
if strings.ContainsRune(funcTypes, k) {
types = append(types, v...)
}
}
sp, tp, err := parsePattern(pattern)
if err != nil {
return fmt.Errorf("failed to parse search pattern: %w", err)
}
res, err := r.Functions(Filter{Schema: sp, Name: tp, Types: types, WithSystem: showSystem})
if err != nil {
return fmt.Errorf("failed to list functions: %w", err)
}
defer res.Close()
if !showSystem {
// in case the reader doesn't implement WithSystem
res.SetFilter(func(r Result) bool {
_, ok := w.systemSchemas[r.(*Function).Schema]
return !ok
})
}
if _, ok := w.r.(FunctionColumnReader); ok {
for res.Next() {
f := res.Get()
f.ArgTypes, err = w.getFunctionColumns(f.Catalog, f.Schema, f.SpecificName)
if err != nil {
return fmt.Errorf("failed to get columns of function %s.%s: %w", f.Schema, f.SpecificName, err)
}
}
res.Reset()
}
columns := []string{"Schema", "Name", "Result data type", "Argument data types", "Type"}
if verbose {
columns = append(columns, "Volatility", "Security", "Language", "Source code")
}
res.SetColumns(columns)
res.SetScanValues(func(r Result) []interface{} {
f := r.(*Function)
v := []interface{}{f.Schema, f.Name, f.ResultType, f.ArgTypes, f.Type}
if verbose {
v = append(v, f.Volatility, f.Security, f.Language, f.Source)
}
return v
})
params := env.Vars().Print()
params["title"] = "List of functions"
return tblfmt.EncodeAll(w.w, res, params)
}
func (w DefaultWriter) getFunctionColumns(c, s, f string) (string, error) {
r := w.r.(FunctionColumnReader)
cols, err := r.FunctionColumns(Filter{Catalog: c, Schema: s, Parent: f})
if err != nil {
return "", err
}
args := []string{}
for cols.Next() {
c := cols.Get()
// skip result params
if c.OrdinalPosition == 0 {
continue
}
typ := ""
if c.Type != "IN" && c.Type != "" {
typ = c.Type + " "
}
name := c.Name
if name != "" {
name += " "
}
args = append(args, fmt.Sprintf("%s%s%s", typ, name, c.DataType))
}
return strings.Join(args, ", "), nil
}
// DescribeTableDetails matching pattern
func (w DefaultWriter) DescribeTableDetails(u *dburl.URL, pattern string, verbose, showSystem bool) error {
sp, tp, err := parsePattern(pattern)
if err != nil {
return fmt.Errorf("failed to parse search pattern: %w", err)
}
found := 0
tr, isTR := w.r.(TableReader)
_, isCR := w.r.(ColumnReader)
if isTR && isCR {
res, err := tr.Tables(Filter{Schema: sp, Name: tp, WithSystem: showSystem})
if err != nil {
return fmt.Errorf("failed to list tables: %w", err)
}
defer res.Close()
if !showSystem {
// in case the reader doesn't implement WithSystem
res.SetFilter(func(r Result) bool {
_, ok := w.systemSchemas[r.(*Table).Schema]
return !ok
})
}
for res.Next() {
t := res.Get()
err = w.describeTableDetails(t.Type, t.Schema, t.Name, verbose, showSystem)
if err != nil {
return fmt.Errorf("failed to describe %s %s.%s: %w", t.Type, t.Schema, t.Name, err)
}
found++
}
}
if _, ok := w.r.(SequenceReader); ok {
foundSeq, err := w.describeSequences(sp, tp, verbose, showSystem)
if err != nil {
return fmt.Errorf("failed to describe sequences: %w", err)
}
found += foundSeq
}
ir, isIR := w.r.(IndexReader)
_, isICR := w.r.(IndexColumnReader)
if isIR && isICR {
res, err := ir.Indexes(Filter{Schema: sp, Name: tp, WithSystem: showSystem})
if err != nil && err != text.ErrNotSupported {
return fmt.Errorf("failed to list indexes for table %s: %w", tp, err)
}
if res != nil {
defer res.Close()
if !showSystem {
// in case the reader doesn't implement WithSystem
res.SetFilter(func(r Result) bool {
_, ok := w.systemSchemas[r.(*Index).Schema]
return !ok
})
}
for res.Next() {
i := res.Get()
err = w.describeIndex(i)
if err != nil {
return fmt.Errorf("failed to describe index %s from table %s.%s: %w", i.Name, i.Schema, i.Table, err)
}
found++
}
}
}
if found == 0 {
fmt.Fprintf(w.w, text.RelationNotFound, pattern)
fmt.Fprintln(w.w)
}
return nil
}
func (w DefaultWriter) describeTableDetails(typ, sp, tp string, verbose, showSystem bool) error {
r := w.r.(ColumnReader)
res, err := r.Columns(Filter{Schema: sp, Parent: tp, WithSystem: showSystem})
if err != nil {
return fmt.Errorf("failed to list columns for table %s: %w", tp, err)
}
defer res.Close()
columns := []string{"Name", "Type", "Nullable", "Default"}
if verbose {
columns = append(columns, "Size", "Decimal Digits", "Radix", "Octet Length")
}
res.SetColumns(columns)
res.SetScanValues(func(r Result) []interface{} {
f := r.(*Column)
v := []interface{}{f.Name, f.DataType, f.IsNullable, f.Default}
if verbose {
v = append(v, f.ColumnSize, f.DecimalDigits, f.NumPrecRadix, f.CharOctetLength)
}
return v
})
params := env.Vars().Print()
params["title"] = fmt.Sprintf("%s %s\n", typ, qualifiedIdentifier(sp, tp))
return w.encodeWithSummary(res, params, w.tableDetailsSummary(sp, tp))
}
func (w DefaultWriter) encodeWithSummary(res tblfmt.ResultSet, params map[string]string, summary func(io.Writer, int) (int, error)) error {
newEnc, opts := tblfmt.FromMap(params)
opts = append(opts, tblfmt.WithSummary(
map[int]func(io.Writer, int) (int, error){
-1: summary,
},
))
enc, err := newEnc(res, opts...)
if err != nil {
return err
}
return enc.EncodeAll(w.w)
}
func (w DefaultWriter) tableDetailsSummary(sp, tp string) func(io.Writer, int) (int, error) {
return func(out io.Writer, _ int) (int, error) {
err := w.describeTableIndexes(out, sp, tp)
if err != nil {
return 0, err
}
err = w.describeTableConstraints(
out,
Filter{Schema: sp, Parent: tp},
func(r Result) bool {
c := r.(*Constraint)
return c.Type == "CHECK" && c.CheckClause != "" && !strings.HasSuffix(c.CheckClause, " IS NOT NULL")
},
"Check constraints:",
func(out io.Writer, c *Constraint) error {
_, err := fmt.Fprintf(out, " \"%s\" %s (%s)\n", c.Name, c.Type, c.CheckClause)
return err
},
)
if err != nil {
return 0, err
}
err = w.describeTableConstraints(
out,
Filter{Schema: sp, Parent: tp},
func(r Result) bool { return r.(*Constraint).Type == "FOREIGN KEY" },
"Foreign-key constraints:",
func(out io.Writer, c *Constraint) error {
columns, foreignColumns, err := w.getConstraintColumns(c.Catalog, c.Schema, c.Table, c.Name)
if err != nil {
return err
}
_, err = fmt.Fprintf(out, " \"%s\" %s (%s) REFERENCES %s(%s) ON UPDATE %s ON DELETE %s\n",
c.Name,
c.Type,
columns,
c.ForeignTable,
foreignColumns,
c.UpdateRule,
c.DeleteRule)
return err
},
)
if err != nil {
return 0, err
}
err = w.describeTableConstraints(
out,
Filter{Schema: sp, Reference: tp},
func(r Result) bool { return r.(*Constraint).Type == "FOREIGN KEY" },
"Referenced by:",
func(out io.Writer, c *Constraint) error {
columns, foreignColumns, err := w.getConstraintColumns(c.Catalog, c.Schema, c.Table, c.Name)
if err != nil {
return err
}
_, err = fmt.Fprintf(out, " TABLE \"%s\" CONSTRAINT \"%s\" %s (%s) REFERENCES %s(%s) ON UPDATE %s ON DELETE %s\n",
c.Table,
c.Name,
c.Type,
columns,
c.ForeignTable,
foreignColumns,
c.UpdateRule,
c.DeleteRule)
return err
},
)
err = w.describeTableTriggers(out, sp, tp)
if err != nil {
return 0, err
}
return 0, err
}
}
func (w DefaultWriter) describeTableTriggers(out io.Writer, sp, tp string) error {
r, ok := w.r.(TriggerReader)
if !ok {
return nil
}
res, err := r.Triggers(Filter{Schema: sp, Parent: tp})
if err != nil && err != text.ErrNotSupported {
return fmt.Errorf("failed to list triggers for table %s: %w", tp, err)
}
if res == nil {
return nil
}
defer res.Close()
if res.Len() == 0 {
return nil
}
fmt.Fprintln(out, "Triggers:")
for res.Next() {
t := res.Get()
fmt.Fprintf(out, " \"%s\" %s\n", t.Name, t.Definition)
}
return nil
}
func (w DefaultWriter) describeTableIndexes(out io.Writer, sp, tp string) error {
r, ok := w.r.(IndexReader)
if !ok {
return nil
}
res, err := r.Indexes(Filter{Schema: sp, Parent: tp})
if err != nil && err != text.ErrNotSupported {
return fmt.Errorf("failed to list indexes for table %s: %w", tp, err)
}
if res == nil {
return nil
}
defer res.Close()
if res.Len() == 0 {
return nil
}
fmt.Fprintln(out, "Indexes:")
for res.Next() {
i := res.Get()
primary := ""
unique := ""
if i.IsPrimary == YES {
primary = "PRIMARY_KEY, "
}
if i.IsUnique == YES {
unique = "UNIQUE, "
}
i.Columns, err = w.getIndexColumns(i.Catalog, i.Schema, i.Table, i.Name)
if err != nil {
return fmt.Errorf("failed to get columns of index %s: %w", i.Name, err)
}
fmt.Fprintf(out, " \"%s\" %s%s%s (%s)\n", i.Name, primary, unique, i.Type, i.Columns)
}
return nil
}
func (w DefaultWriter) getIndexColumns(c, s, t, i string) (string, error) {
r := w.r.(IndexColumnReader)
cols, err := r.IndexColumns(Filter{Catalog: c, Schema: s, Parent: t, Name: i})
if err != nil {
return "", err
}
result := []string{}
for cols.Next() {
result = append(result, cols.Get().Name)
}
return strings.Join(result, ", "), nil
}
func (w DefaultWriter) describeTableConstraints(out io.Writer, filter Filter, postFilter func(r Result) bool, label string, printer func(io.Writer, *Constraint) error) error {
r, ok := w.r.(ConstraintReader)
if !ok {
return nil
}
res, err := r.Constraints(filter)
if err != nil && err != text.ErrNotSupported {
return fmt.Errorf("failed to list constraints: %w", err)
}
if res == nil {
return nil
}
defer res.Close()
res.SetFilter(postFilter)
if res.Len() == 0 {
return nil
}
fmt.Fprintln(out, label)
for res.Next() {
c := res.Get()
err := printer(out, c)
if err != nil {
return err
}
}
return nil
}
func (w DefaultWriter) getConstraintColumns(c, s, t, n string) (string, string, error) {
r := w.r.(ConstraintColumnReader)
cols, err := r.ConstraintColumns(Filter{Catalog: c, Schema: s, Parent: t, Name: n})
if err != nil {
return "", "", err
}
columns := []string{}
foreignColumns := []string{}
for cols.Next() {
columns = append(columns, cols.Get().Name)
foreignColumns = append(foreignColumns, cols.Get().ForeignName)
}
return strings.Join(columns, ", "), strings.Join(foreignColumns, ", "), nil
}
func (w DefaultWriter) describeSequences(sp, tp string, verbose, showSystem bool) (int, error) {
r := w.r.(SequenceReader)
res, err := r.Sequences(Filter{Schema: sp, Name: tp, WithSystem: showSystem})
if err != nil && err != text.ErrNotSupported {
return 0, err
}
if res == nil {
return 0, nil
}
defer res.Close()
found := 0
for res.Next() {
s := res.Get()
// wrap current record into a separate recordSet
rows := NewSequenceSet([]Sequence{*s})
params := env.Vars().Print()
params["footer"] = "off"
params["title"] = fmt.Sprintf("Sequence \"%s.%s\"\n", s.Schema, s.Name)
err = tblfmt.EncodeAll(w.w, rows, params)
if err != nil {
return 0, err
}
// TODO footer should say which table this sequence belongs to
found++
}
return found, nil
}
func (w DefaultWriter) describeIndex(i *Index) error {
r := w.r.(IndexColumnReader)
res, err := r.IndexColumns(Filter{Schema: i.Schema, Parent: i.Table, Name: i.Name})
if err != nil {
return fmt.Errorf("failed to get index columns: %w", err)
}
defer res.Close()
if res.Len() == 0 {
return nil
}
res.SetColumns([]string{"Name", "Type"})
res.SetScanValues(func(r Result) []interface{} {
f := r.(*IndexColumn)
return []interface{}{f.Name, f.DataType}
})
params := env.Vars().Print()
params["title"] = fmt.Sprintf("Index %s\n", qualifiedIdentifier(i.Schema, i.Name))
return w.encodeWithSummary(res, params, func(out io.Writer, _ int) (int, error) {
primary := ""
if i.IsPrimary == YES {
primary = "primary key, "
}
_, err := fmt.Fprintf(out, "%s%s, for table %s", primary, i.Type, i.Table)
return 0, err
})
}
// ListAllDbs matching pattern
func (w DefaultWriter) ListAllDbs(u *dburl.URL, pattern string, verbose bool) error {
if w.listAllDbs != nil {
return w.listAllDbs(pattern, verbose)
}
r, ok := w.r.(CatalogReader)
if !ok {
return fmt.Errorf(text.NotSupportedByDriver, `\l`, u.Driver)
}
res, err := r.Catalogs(Filter{Name: pattern})
if err != nil {
return fmt.Errorf("failed to list catalogs: %w", err)
}
defer res.Close()
params := env.Vars().Print()
params["title"] = "List of databases"
return tblfmt.EncodeAll(w.w, res, params)
}
// ListTables matching pattern
func (w DefaultWriter) ListTables(u *dburl.URL, tableTypes, pattern string, verbose, showSystem bool) error {
r, ok := w.r.(TableReader)
if !ok {
return fmt.Errorf(text.NotSupportedByDriver, `\dt`, u.Driver)
}
types := []string{}
for k, v := range w.tableTypes {
if strings.ContainsRune(tableTypes, k) {
types = append(types, v...)
}
}
sp, tp, err := parsePattern(pattern)
if err != nil {
return fmt.Errorf("failed to parse search pattern: %w", err)
}
res, err := r.Tables(Filter{Schema: sp, Name: tp, Types: types, WithSystem: showSystem})
if err != nil {
return fmt.Errorf("failed to list tables: %w", err)
}
defer res.Close()
if !showSystem {
// in case the reader doesn't implement WithSystem
res.SetFilter(func(r Result) bool {
_, ok := w.systemSchemas[r.(*Table).Schema]
return !ok
})
}
if res.Len() == 0 {
fmt.Fprintf(w.w, text.RelationNotFound, pattern)
fmt.Fprintln(w.w)
return nil
}
columns := []string{"Schema", "Name", "Type"}
if verbose {
columns = append(columns, "Rows", "Size", "Comment")
}
res.SetColumns(columns)
res.SetScanValues(func(r Result) []interface{} {
f := r.(*Table)
v := []interface{}{f.Schema, f.Name, f.Type}
if verbose {
v = append(v, f.Rows, f.Size, f.Comment)
}
return v
})
params := env.Vars().Print()
params["title"] = "List of relations"
return tblfmt.EncodeAll(w.w, res, params)
}
// ListSchemas matching pattern
func (w DefaultWriter) ListSchemas(u *dburl.URL, pattern string, verbose, showSystem bool) error {
r, ok := w.r.(SchemaReader)
if !ok {
return fmt.Errorf(text.NotSupportedByDriver, `\d`, u.Driver)
}
res, err := r.Schemas(Filter{Name: pattern, WithSystem: showSystem})
if err != nil {
return fmt.Errorf("failed to list schemas: %w", err)
}
defer res.Close()
if !showSystem {
// in case the reader doesn't implement WithSystem
res.SetFilter(func(r Result) bool {
_, ok := w.systemSchemas[r.(*Schema).Schema]
return !ok
})
}
params := env.Vars().Print()
params["title"] = "List of schemas"
return tblfmt.EncodeAll(w.w, res, params)
}
// ListIndexes matching pattern
func (w DefaultWriter) ListIndexes(u *dburl.URL, pattern string, verbose, showSystem bool) error {
r, ok := w.r.(IndexReader)
if !ok {
return fmt.Errorf(text.NotSupportedByDriver, `\di`, u.Driver)
}
sp, tp, err := parsePattern(pattern)
if err != nil {
return fmt.Errorf("failed to parse search pattern: %w", err)
}
res, err := r.Indexes(Filter{Schema: sp, Name: tp, WithSystem: showSystem})
if err != nil {
return fmt.Errorf("failed to list indexes: %w", err)
}
defer res.Close()
if !showSystem {
// in case the reader doesn't implement WithSystem
res.SetFilter(func(r Result) bool {
_, ok := w.systemSchemas[r.(*Index).Schema]
return !ok
})
}
if res.Len() == 0 {
fmt.Fprintf(w.w, text.RelationNotFound, pattern)
fmt.Fprintln(w.w)
return nil
}
columns := []string{"Schema", "Name", "Type", "Table"}
if verbose {
columns = append(columns, "Primary?", "Unique?")
}
res.SetColumns(columns)
res.SetScanValues(func(r Result) []interface{} {
f := r.(*Index)
v := []interface{}{f.Schema, f.Name, f.Type, f.Table}
if verbose {
v = append(v, f.IsPrimary, f.IsUnique)
}
return v
})
params := env.Vars().Print()
params["title"] = "List of indexes"
return tblfmt.EncodeAll(w.w, res, params)
}
// ShowStats of columns for tables matching pattern
func (w DefaultWriter) ShowStats(u *dburl.URL, statTypes, pattern string, verbose bool, k int) error {
r, ok := w.r.(ColumnStatReader)
if !ok {
return fmt.Errorf(text.NotSupportedByDriver, `\ss`, u.Driver)
}
sp, tp, err := parsePattern(pattern)
if err != nil {
return fmt.Errorf("failed to parse search pattern: %w", err)
}
rows := int64(0)
tr, ok := w.r.(TableReader)
if ok {
tables, err := tr.Tables(Filter{Schema: sp, Name: tp})
if err != nil {
return fmt.Errorf("failed to get table entry: %w", err)
}
defer tables.Close()
if tables.Next() {
rows = tables.Get().Rows
}
}
types := []string{"basic"}
if verbose {
types = append(types, "extended")
}
res, err := r.ColumnStats(Filter{Schema: sp, Parent: tp, Types: types})
if err != nil {
return fmt.Errorf("failed to get column stats: %w", err)
}
defer res.Close()
if res.Len() == 0 {
fmt.Fprintf(w.w, text.RelationNotFound, pattern)
fmt.Fprintln(w.w)
return nil
}
columns := []string{"Schema", "Table", "Name", "Average width", "Nulls fraction", "Distinct values", "Dist. fraction"}
if verbose {
columns = append(columns, "Minimum value", "Maximum value", "Mean value", "Top N common values", "Top N values freqs")
}
res.SetColumns(columns)
res.SetScanValues(func(r Result) []interface{} {
f := r.(*ColumnStat)
freqs := []string{}
for _, freq := range f.TopNFreqs {
freqs = append(freqs, fmt.Sprintf("%.4f", freq))
}
n := k
if n > len(freqs) {
n = len(freqs)
}
distFrac := 1.0
if rows != 0 && f.NumDistinct != rows {
distFrac = float64(f.NumDistinct) / float64(rows)
}
v := []interface{}{
f.Schema,
f.Table,
f.Name,
f.AvgWidth,
f.NullFrac,
f.NumDistinct,
fmt.Sprintf("%.4f", distFrac),
}
if verbose {
v = append(v,
f.Min,
f.Max,
f.Mean,
strings.Join(f.TopN[:n], ", "),
strings.Join(freqs[:n], ", "),
)
}
return v
})
params := env.Vars().Print()
params["title"] = "Column stats"
return tblfmt.EncodeAll(w.w, res, params)
}
// ListPrivilegeSummaries matching pattern
func (w DefaultWriter) ListPrivilegeSummaries(u *dburl.URL, pattern string, showSystem bool) error {
r, ok := w.r.(PrivilegeSummaryReader)
if !ok {
return fmt.Errorf(text.NotSupportedByDriver, `\dp`, u.Driver)
}
sp, tp, err := parsePattern(pattern)
if err != nil {
return fmt.Errorf("failed to parse search pattern: %w", err)
}
// filter for tables, views and sequences
const tableTypes = "tvms"
types := []string{}
for k, v := range w.tableTypes {
if strings.ContainsRune(tableTypes, k) {
types = append(types, v...)
}
}
res, err := r.PrivilegeSummaries(Filter{Schema: sp, Name: tp, WithSystem: showSystem, Types: types})
if err != nil {
return fmt.Errorf("failed to list table privileges: %w", err)
}
defer res.Close()
if !showSystem {
// in case the reader doesn't implement WithSystem
res.SetFilter(func(r Result) bool {
_, ok := w.systemSchemas[r.(*PrivilegeSummary).Schema]
return !ok
})
}
res.SetScanValues(func(r Result) []interface{} {
f := r.(*PrivilegeSummary)
v := []interface{}{
f.Schema,
f.Name,
f.ObjectType,
f.ObjectPrivileges,
f.ColumnPrivileges,
}
return v
})
params := env.Vars().Print()
params["title"] = "Access privileges"
return tblfmt.EncodeAll(w.w, res, params)
}
func parsePattern(pattern string) (string, string, error) {
// TODO do proper escaping, quoting etc
if strings.ContainsRune(pattern, '.') {
parts := strings.SplitN(pattern, ".", 2)
return strings.ReplaceAll(parts[0], "*", "%"), strings.ReplaceAll(parts[1], "*", "%"), nil
}
return "", strings.ReplaceAll(pattern, "*", "%"), nil
}
func qualifiedIdentifier(schema, name string) string {
if schema == "" {
return fmt.Sprintf("\"%s\"", name)
}
return fmt.Sprintf("\"%s.%s\"", schema, name)
}
================================================
FILE: drivers/moderncsqlite/moderncsqlite.go
================================================
// Package moderncsqlite defines and registers usql's ModernC SQLite3 driver.
// Transpilation of SQLite3 to Go.
//
// See: https://gitlab.com/cznic/sqlite
package moderncsqlite
import (
"context"
"database/sql"
"io"
"strconv"
"github.com/xo/dburl"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/sqlite3/sqshared"
"modernc.org/sqlite" // DRIVER
)
func init() {
drivers.Register("moderncsqlite", drivers.Driver{
AllowMultilineComments: true,
Open: func(_ context.Context, u *dburl.URL, stdout, stderr func() io.Writer) (func(string, string) (*sql.DB, error), error) {
return func(_ string, params string) (*sql.DB, error) {
return sql.Open("sqlite", params)
}, nil
},
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
err := db.QueryRowContext(ctx, `SELECT sqlite_version()`).Scan(&ver)
if err != nil {
return "", err
}
return "ModernC SQLite " + ver, nil
},
Err: func(err error) (string, string) {
if e, ok := err.(*sqlite.Error); ok {
return strconv.Itoa(e.Code()), e.Error()
}
return "", err.Error()
},
ConvertBytes: sqshared.ConvertBytes,
NewMetadataReader: sqshared.NewMetadataReader,
Copy: drivers.CopyWithInsert(func(int) string { return "?" }),
})
}
================================================
FILE: drivers/mymysql/mymysql.go
================================================
// Package mymysql defines and registers usql's MySQL MyMySQL driver.
//
// See: https://github.com/ziutek/mymysql
package mymysql
import (
"io"
"strconv"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
mymeta "github.com/xo/usql/drivers/metadata/mysql"
_ "github.com/ziutek/mymysql/godrv" // DRIVER
"github.com/ziutek/mymysql/mysql"
)
func init() {
drivers.Register("mymysql", drivers.Driver{
AllowMultilineComments: true,
AllowHashComments: true,
LexerName: "mysql",
UseColumnTypes: true,
Err: func(err error) (string, string) {
if e, ok := err.(*mysql.Error); ok {
return strconv.Itoa(int(e.Code)), string(e.Msg)
}
return "", err.Error()
},
IsPasswordErr: func(err error) bool {
if e, ok := err.(*mysql.Error); ok {
return e.Code == mysql.ER_ACCESS_DENIED_ERROR
}
return false
},
NewMetadataReader: mymeta.NewReader,
NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer {
return metadata.NewDefaultWriter(mymeta.NewReader(db, opts...))(db, w)
},
Copy: drivers.CopyWithInsert(func(int) string { return "?" }),
NewCompleter: mymeta.NewCompleter,
})
}
================================================
FILE: drivers/mysql/mysql.go
================================================
// Package mysql defines and registers usql's MySQL driver.
//
// Alias: memsql, SingleStore MemSQL
// Alias: vitess, Vitess Database
// Alias: tidb, TiDB
//
// See: https://github.com/go-sql-driver/mysql
// Group: base
package mysql
import (
"io"
"strconv"
"github.com/go-sql-driver/mysql" // DRIVER
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
mymeta "github.com/xo/usql/drivers/metadata/mysql"
)
func init() {
drivers.Register("mysql", drivers.Driver{
AllowMultilineComments: true,
AllowHashComments: true,
LexerName: "mysql",
UseColumnTypes: true,
ForceParams: drivers.ForceQueryParameters([]string{
"parseTime", "true",
"loc", "Local",
"sql_mode", "ansi",
}),
Err: func(err error) (string, string) {
if e, ok := err.(*mysql.MySQLError); ok {
return strconv.Itoa(int(e.Number)), e.Message
}
return "", err.Error()
},
IsPasswordErr: func(err error) bool {
if e, ok := err.(*mysql.MySQLError); ok {
return e.Number == 1045
}
return false
},
NewMetadataReader: mymeta.NewReader,
NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer {
return metadata.NewDefaultWriter(mymeta.NewReader(db, opts...))(db, w)
},
Copy: drivers.CopyWithInsert(func(int) string { return "?" }),
NewCompleter: mymeta.NewCompleter,
}, "memsql", "vitess", "tidb")
}
================================================
FILE: drivers/netezza/netezza.go
================================================
// Package netezza defines and registers usql's Netezza driver.
//
// See: https://github.com/IBM/nzgo
package netezza
import (
"context"
"io"
"log"
"github.com/IBM/nzgo/v12" // DRIVER: nzgo
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
infos "github.com/xo/usql/drivers/metadata/informationschema"
)
func init() {
nzgo.Debug = log.New(io.Discard, "", 0)
nzgo.Info = log.New(io.Discard, "", 0)
nzgo.Fatal = log.New(io.Discard, "", 0)
newReader := infos.New(
infos.WithPlaceholder(func(int) string { return "?" }),
infos.WithIndexes(false),
infos.WithConstraints(false),
infos.WithCustomClauses(map[infos.ClauseName]string{
infos.ColumnsColumnSize: "COALESCE(character_maximum_length, numeric_precision, datetime_precision, interval_precision, 0)",
infos.FunctionColumnsColumnSize: "COALESCE(character_maximum_length, numeric_precision, datetime_precision, interval_precision, 0)",
}),
infos.WithSystemSchemas([]string{"DEFINITION_SCHEMA", "INFORMATION_SCHEMA"}),
infos.WithCurrentSchema("CURRENT_SCHEMA"),
)
drivers.Register("nzgo", drivers.Driver{
Name: "nz",
AllowDollar: true,
AllowMultilineComments: true,
LexerName: "postgres",
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
err := db.QueryRowContext(ctx, `SELECT version()`).Scan(&ver)
if err != nil {
return "", err
}
return "Netezza " + ver, nil
},
ChangePassword: func(db drivers.DB, user, newpw, _ string) error {
_, err := db.Exec(`ALTER USER ` + user + ` PASSWORD '` + newpw + `'`)
return err
},
Err: func(err error) (string, string) {
if e, ok := err.(*nzgo.Error); ok {
return string(e.Code), e.Message
}
return "", err.Error()
},
IsPasswordErr: func(err error) bool {
if e, ok := err.(*nzgo.Error); ok {
return e.Code.Name() == "invalid_password"
}
return false
},
NewMetadataReader: newReader,
NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer {
return metadata.NewDefaultWriter(newReader(db, opts...))(db, w)
},
})
}
================================================
FILE: drivers/odbc/odbc.go
================================================
// Package odbc defines and registers usql's ODBC driver. Requires CGO. Uses
// respective platform's standard ODBC packages.
//
// See: https://github.com/alexbrainman/odbc
// Group: all
package odbc
import (
"regexp"
"strings"
"github.com/alexbrainman/odbc" // DRIVER
"github.com/xo/dburl"
"github.com/xo/usql/drivers"
)
func init() {
endRE := regexp.MustCompile(`;?\s*$`)
endAnchorRE := regexp.MustCompile(`(?i)\send\s*;\s*$`)
drivers.Register("odbc", drivers.Driver{
LexerName: "tsql",
Process: func(u *dburl.URL, prefix string, sqlstr string) (string, string, bool, error) {
// trim last ; but only when not END;
if s := strings.ToLower(u.Query().Get("usql_trim")); s != "" && s != "off" && s != "0" && s != "false" {
if !endAnchorRE.MatchString(sqlstr) {
sqlstr = endRE.ReplaceAllString(sqlstr, "")
}
}
typ, q := drivers.QueryExecType(prefix, sqlstr)
return typ, sqlstr, q, nil
},
IsPasswordErr: func(err error) bool {
if e, ok := err.(*odbc.Error); ok {
msg := strings.ToLower(e.Error())
return strings.Contains(msg, "failed") &&
(strings.Contains(msg, "login") ||
strings.Contains(msg, "authentication") ||
strings.Contains(msg, "password"))
}
return false
},
})
}
================================================
FILE: drivers/oracle/oracle.go
================================================
// Package oracle defines and registers usql's Oracle Database driver.
//
// See: https://github.com/sijms/go-ora
// Group: base
package oracle
import (
"errors"
"fmt"
"strings"
_ "github.com/sijms/go-ora/v2" // DRIVER
"github.com/xo/usql/drivers/oracle/orshared"
)
func init() {
orshared.Register(
"oracle",
// unwrap error
func(err error) (string, string) {
if e := errors.Unwrap(err); e != nil {
err = e
}
code, msg := "", err.Error()
if e, ok := err.(interface {
Code() int
}); ok {
code = fmt.Sprintf("ORA-%05d", e.Code())
}
if e, ok := err.(interface {
Message() string
}); ok {
msg = e.Message()
}
if i := strings.LastIndex(msg, "ORA-"); msg == "" && i != -1 {
msg = msg[i:]
if j := strings.Index(msg, ":"); j != -1 {
msg = msg[j+1:]
if code == "" {
code = msg[i:j]
}
}
}
return code, strings.TrimSpace(msg)
},
// is password error
func(err error) bool {
if e := errors.Unwrap(err); e != nil {
err = e
}
return strings.Contains(err.Error(), "empty password")
},
)
}
================================================
FILE: drivers/oracle/orshared/orshared.go
================================================
// Package orshared contains shared a shared driver implementation for the
// Oracle Database. Used by Oracle and Godror drivers.
package orshared
import (
"context"
"fmt"
"io"
"regexp"
"strings"
"github.com/xo/dburl"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
orameta "github.com/xo/usql/drivers/metadata/oracle"
"github.com/xo/usql/env"
)
// Register registers an oracle driver.
func Register(name string, err func(error) (string, string), isPasswordErr func(error) bool) {
endRE := regexp.MustCompile(`;?\s*$`)
endAnchorRE := regexp.MustCompile(`(?i)\send\s*;\s*$`)
drivers.Register(name, drivers.Driver{
AllowMultilineComments: true,
LowerColumnNames: true,
ForceParams: func(u *dburl.URL) {
// if the service name is not specified, use the environment
// variable if present
if strings.TrimPrefix(u.Path, "/") == "" {
if n, ok := env.Getenv("ORACLE_SID", "ORASID"); ok && n != "" {
u.Path = "/" + n
if u.Host == "" {
u.Host = "localhost"
}
}
}
},
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
if err := db.QueryRowContext(ctx, `SELECT version FROM v$instance`).Scan(&ver); err != nil {
return "", err
}
return "Oracle Database " + ver, nil
},
User: func(ctx context.Context, db drivers.DB) (string, error) {
var user string
if err := db.QueryRowContext(ctx, `SELECT user FROM dual`).Scan(&user); err != nil {
return "", err
}
return user, nil
},
ChangePassword: func(db drivers.DB, user, newpw, _ string) error {
_, err := db.Exec(`ALTER USER ` + user + ` IDENTIFIED BY ` + newpw)
return err
},
Err: err,
IsPasswordErr: isPasswordErr,
Process: func(_ *dburl.URL, prefix string, sqlstr string) (string, string, bool, error) {
if !endAnchorRE.MatchString(sqlstr) {
// trim last ; but only when not END;
sqlstr = endRE.ReplaceAllString(sqlstr, "")
}
typ, q := drivers.QueryExecType(prefix, sqlstr)
return typ, sqlstr, q, nil
},
NewMetadataReader: orameta.NewReader(),
NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer {
return metadata.NewDefaultWriter(orameta.NewReader()(db, opts...))(db, w)
},
Copy: drivers.CopyWithInsert(func(n int) string {
return fmt.Sprintf(":%d", n)
}),
})
}
================================================
FILE: drivers/ots/ots.go
================================================
// Package ots defines and registers usql's Alibaba Tablestore driver.
//
// See: https://github.com/aliyun/aliyun-tablestore-go-sql-driver
package ots
import (
_ "github.com/aliyun/aliyun-tablestore-go-sql-driver" // DRIVER
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("ots", drivers.Driver{})
}
================================================
FILE: drivers/pgx/pgx.go
================================================
// Package pgx defines and registers usql's PostgreSQL PGX driver.
//
// See: https://github.com/jackc/pgx
package pgx
import (
"context"
"database/sql"
"errors"
"fmt"
"io"
"strings"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/stdlib" // DRIVER
"github.com/xo/dburl"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
pgmeta "github.com/xo/usql/drivers/metadata/postgres"
"github.com/xo/usql/text"
)
func init() {
drivers.Register("pgx", drivers.Driver{
AllowDollar: true,
AllowMultilineComments: true,
LexerName: "postgres",
Open: func(ctx context.Context, u *dburl.URL, stdout, stderr func() io.Writer) (func(string, string) (*sql.DB, error), error) {
return func(_, dsn string) (*sql.DB, error) {
config, err := pgx.ParseConfig(dsn)
if err != nil {
return nil, err
}
config.OnNotice = func(_ *pgconn.PgConn, notice *pgconn.Notice) {
out := stderr()
fmt.Fprintln(out, notice.Severity+": ", notice.Message)
if notice.Hint != "" {
fmt.Fprintln(out, "HINT: ", notice.Hint)
}
}
config.OnNotification = func(_ *pgconn.PgConn, notification *pgconn.Notification) {
var payload string
if notification.Payload != "" {
payload = fmt.Sprintf(text.NotificationPayload, notification.Payload)
}
fmt.Fprintln(stdout(), fmt.Sprintf(text.NotificationReceived, notification.Channel, payload, notification.PID))
}
// NOTE: as opposed to the github.com/lib/pq driver, this
// NOTE: driver has a "prefer" mode that is enabled by default.
// NOTE: as such there is no logic here to try to reconnect as
// NOTE: in the postgres driver.
return stdlib.OpenDB(*config), nil
}, nil
},
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
err := db.QueryRowContext(ctx, `SHOW server_version`).Scan(&ver)
if err != nil {
return "", err
}
return "PostgreSQL " + ver, nil
},
ChangePassword: func(db drivers.DB, user, newpw, _ string) error {
_, err := db.Exec(`ALTER USER ` + user + ` PASSWORD '` + newpw + `'`)
return err
},
Err: func(err error) (string, string) {
var e *pgconn.PgError
if errors.As(err, &e) {
return e.Code, e.Message
}
return "", err.Error()
},
IsPasswordErr: func(err error) bool {
var e *pgconn.PgError
if errors.As(err, &e) {
return e.Code == "28P01"
}
return false
},
NewMetadataReader: pgmeta.NewReader(),
NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer {
return metadata.NewDefaultWriter(pgmeta.NewReader()(db, opts...))(db, w)
},
Copy: func(ctx context.Context, db *sql.DB, rows *sql.Rows, table string) (int64, error) {
conn, err := db.Conn(context.Background())
if err != nil {
return 0, fmt.Errorf("failed to get a connection from pool: %w", err)
}
leftParen := strings.IndexRune(table, '(')
colQuery := "SELECT * FROM " + table + " WHERE 1=0"
if leftParen != -1 {
// pgx's CopyFrom needs a slice of column names and splitting them by a comma is unreliable
// so evaluate the possible expressions against the target table
colQuery = "SELECT " + table[leftParen+1:len(table)-1] + " FROM " + table[:leftParen] + " WHERE 1=0"
table = table[:leftParen]
}
colStmt, err := db.PrepareContext(ctx, colQuery)
if err != nil {
return 0, fmt.Errorf("failed to prepare query to determine target table columns: %w", err)
}
colRows, err := colStmt.QueryContext(ctx)
if err != nil {
return 0, fmt.Errorf("failed to execute query to determine target table columns: %w", err)
}
columns, err := colRows.Columns()
if err != nil {
return 0, fmt.Errorf("failed to fetch target table columns: %w", err)
}
clen := len(columns)
crows := ©Rows{
rows: rows,
values: make([]interface{}, clen),
}
for i := 0; i < clen; i++ {
crows.values[i] = new(interface{})
}
var n int64
err = conn.Raw(func(driverConn interface{}) error {
conn := driverConn.(*stdlib.Conn).Conn()
n, err = conn.CopyFrom(ctx, pgx.Identifier(strings.SplitN(table, ".", 2)), columns, crows)
return err
})
return n, err
},
})
}
type copyRows struct {
rows *sql.Rows
values []interface{}
}
func (r *copyRows) Next() bool {
return r.rows.Next()
}
func (r *copyRows) Values() ([]interface{}, error) {
err := r.rows.Scan(r.values...)
actuals := make([]interface{}, len(r.values))
for i, v := range r.values {
actuals[i] = *(v.(*interface{}))
}
return actuals, err
}
func (r *copyRows) Err() error {
return r.rows.Err()
}
================================================
FILE: drivers/postgres/postgres.go
================================================
// Package postgres defines and registers usql's PostgreSQL driver.
//
// Alias: cockroachdb, CockroachDB
// Alias: redshift, Amazon Redshift
//
// See: https://github.com/lib/pq
// Group: base
package postgres
import (
"context"
"database/sql"
"errors"
"fmt"
"io"
"strings"
"github.com/lib/pq" // DRIVER
"github.com/xo/dburl"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
pgmeta "github.com/xo/usql/drivers/metadata/postgres"
"github.com/xo/usql/env"
"github.com/xo/usql/text"
)
func init() {
openConn := func(stdout, stderr func() io.Writer, dsn string) (*sql.DB, error) {
conn, err := pq.NewConnector(dsn)
if err != nil {
return nil, err
}
noticeConn := pq.ConnectorWithNoticeHandler(conn, func(notice *pq.Error) {
out := stderr()
fmt.Fprintln(out, notice.Severity+": ", notice.Message)
if notice.Hint != "" {
fmt.Fprintln(out, "HINT: ", notice.Hint)
}
})
notificationConn := pq.ConnectorWithNotificationHandler(noticeConn, func(notification *pq.Notification) {
var payload string
if notification.Extra != "" {
payload = fmt.Sprintf(text.NotificationPayload, notification.Extra)
}
fmt.Fprintln(stdout(), fmt.Sprintf(text.NotificationReceived, notification.Channel, payload, notification.BePid))
})
return sql.OpenDB(notificationConn), nil
}
drivers.Register("postgres", drivers.Driver{
Name: "pq",
AllowDollar: true,
AllowMultilineComments: true,
LexerName: "postgres",
ForceParams: func(u *dburl.URL) {
if u.Scheme == "cockroachdb" {
drivers.ForceQueryParameters([]string{"sslmode", "disable"})(u)
}
},
Open: func(ctx context.Context, u *dburl.URL, stdout, stderr func() io.Writer) (func(string, string) (*sql.DB, error), error) {
return func(_, dsn string) (*sql.DB, error) {
conn, err := openConn(stdout, stderr, dsn)
if err != nil {
return nil, err
}
// special retry handling case, since there's no lib/pq retry mode
if env.Get("SSLMODE") == "retry" && !u.Query().Has("sslmode") {
switch err = conn.PingContext(ctx); {
case errors.Is(err, pq.ErrSSLNotSupported):
s := "sslmode=disable " + dsn
conn, err = openConn(stdout, stderr, s)
if err != nil {
return nil, err
}
u.DSN = s
case err != nil:
return nil, err
}
}
return conn, nil
}, nil
},
Version: func(ctx context.Context, db drivers.DB) (string, error) {
// numeric version
// SHOW server_version_num;
var ver string
err := db.QueryRowContext(ctx, `SHOW server_version`).Scan(&ver)
if err != nil {
return "", err
}
return "PostgreSQL " + ver, nil
},
ChangePassword: func(db drivers.DB, user, newpw, _ string) error {
_, err := db.Exec(`ALTER USER ` + user + ` PASSWORD '` + newpw + `'`)
return err
},
Err: func(err error) (string, string) {
if e, ok := err.(*pq.Error); ok {
return string(e.Code), e.Message
}
return "", err.Error()
},
IsPasswordErr: func(err error) bool {
if e, ok := err.(*pq.Error); ok {
return e.Code.Name() == "invalid_password"
}
return false
},
NewMetadataReader: pgmeta.NewReader(),
NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer {
return metadata.NewDefaultWriter(pgmeta.NewReader()(db, opts...))(db, w)
},
Copy: func(ctx context.Context, db *sql.DB, rows *sql.Rows, table string) (int64, error) {
columns, err := rows.Columns()
if err != nil {
return 0, fmt.Errorf("failed to fetch source rows columns: %w", err)
}
clen := len(columns)
query := table
if !strings.HasPrefix(strings.ToLower(query), "insert into") {
leftParen := strings.IndexRune(table, '(')
colQuery := "SELECT * FROM " + table + " WHERE 1=0"
if leftParen != -1 {
colQuery = "SELECT " + table[leftParen+1:len(table)-1] + " FROM " + table[:leftParen] + " WHERE 1=0"
table = table[:leftParen]
}
colStmt, err := db.PrepareContext(ctx, colQuery)
if err != nil {
return 0, fmt.Errorf("failed to prepare query to determine target table columns: %w", err)
}
defer colStmt.Close()
colRows, err := colStmt.QueryContext(ctx)
if err != nil {
return 0, fmt.Errorf("failed to execute query to determine target table columns: %w", err)
}
columns, err := colRows.Columns()
if err != nil {
return 0, fmt.Errorf("failed to fetch target table columns: %w", err)
}
if schemaSep := strings.Index(table, "."); schemaSep >= 0 {
query = pq.CopyInSchema(table[:schemaSep], table[schemaSep+1:], columns...)
} else {
query = pq.CopyIn(table, columns...)
}
}
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
stmt, err := tx.PrepareContext(ctx, query)
if err != nil {
return 0, fmt.Errorf("failed to prepare insert query: %w", err)
}
defer stmt.Close()
values := make([]interface{}, clen)
for i := 0; i < clen; i++ {
values[i] = new(interface{})
}
var n int64
for rows.Next() {
err = rows.Scan(values...)
if err != nil {
return n, fmt.Errorf("failed to scan row: %w", err)
}
_, err := stmt.ExecContext(ctx, values...)
if err != nil {
return n, fmt.Errorf("failed to exec copy: %w", err)
}
}
res, err := stmt.ExecContext(ctx)
if err != nil {
return n, fmt.Errorf("failed to final exec copy: %w", err)
}
rn, err := res.RowsAffected()
if err != nil {
return n, fmt.Errorf("failed to check rows affected: %w", err)
}
n += rn
err = tx.Commit()
if err != nil {
return n, fmt.Errorf("failed to commit transaction: %w", err)
}
return n, rows.Err()
},
}, "cockroachdb", "redshift")
}
================================================
FILE: drivers/presto/presto.go
================================================
// Package presto defines and registers usql's Presto driver.
//
// See: https://github.com/prestodb/presto-go-client
package presto
import (
"context"
_ "github.com/prestodb/presto-go-client/presto" // DRIVER
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("presto", drivers.Driver{
AllowMultilineComments: true,
Process: drivers.StripTrailingSemicolon,
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
err := db.QueryRowContext(
ctx,
`SELECT node_version FROM system.runtime.nodes LIMIT 1`,
).Scan(&ver)
if err != nil {
return "", err
}
return "Presto " + ver, nil
},
})
}
================================================
FILE: drivers/ql/ql.go
================================================
// Package ql defines and registers usql's Cznic QL driver.
//
// See: https://gitlab.com/cznic/ql
package ql
import (
"github.com/xo/usql/drivers"
"modernc.org/ql" // DRIVER
)
func init() {
ql.RegisterDriver()
// ql.RegisterMemDriver()
drivers.Register("ql", drivers.Driver{
AllowMultilineComments: true,
AllowCComments: true,
BatchQueryPrefixes: map[string]string{
"BEGIN TRANSACTION": "COMMIT",
},
BatchAsTransaction: true,
})
}
================================================
FILE: drivers/qtype.go
================================================
package drivers
import (
"strings"
)
// queryMap is the map of SQL prefixes use as queries.
var queryMap = map[string]bool{
"WITH": true,
"PRAGMA": true,
"EXPLAIN": true, // show the execution plan of a statement
"DESCRIBE": true, // describe (mysql)
"DESC": true, // describe (mysql)
"FETCH": true, // retrieve rows from a query using a cursor
"SELECT": true, // retrieve rows from a table or view
"SHOW": true, // show the value of a run-time parameter
"ADMIN SHOW": true,
"VALUES": true, // compute a set of rows
"LIST": true, // list permissions, roles, users (cassandra)
"EXEC": true, // execute a stored procedure that returns rows (not postgres)
"TABLE": true, // shortcut for select * from (postgresql)
"CALL": true,
"FROM": true,
}
// execMap is the map of SQL prefixes to execute.
//
// Unless noted, these are extracted from the PostgreSQL docs.
//
// Note: originally extracted via a script, but maintained by hand as the
// documentation for any new queries introduced by PostgreSQL need to be
// manually scrutinized for variations.
var execMap = map[string]bool{
// cassandra
"ALTER KEYSPACE": true, // alter a keyspace
"CREATE KEYSPACE": true, // create a keyspace
"DROP KEYSPACE": true, // drop a keyspace
"BEGIN BATCH": true, // begin batch
"APPLY BATCH": true, // apply batch
// sqlserver
"CREATE LOGIN": true, // create login
"CREATE PROCEDURE": true, // create procedure
"DROP LOGIN": true, // drop login
"DROP PROCEDURE": true, // drop procedure
// ql
"BEGIN TRANSACTION": true, // begin batch
// postgresql
"ABORT": true, // abort the current transaction
"ALTER AGGREGATE": true, // change the definition of an aggregate function
"ALTER COLLATION": true, // change the definition of a collation
"ALTER CONVERSION": true, // change the definition of a conversion
"ALTER DATABASE": true, // change a database
"ALTER DEFAULT PRIVILEGES": true, // define default access privileges
"ALTER DOMAIN": true, // change the definition of a domain
"ALTER EVENT TRIGGER": true, // change the definition of an event trigger
"ALTER EXTENSION": true, // change the definition of an extension
"ALTER FOREIGN DATA WRAPPER": true, // change the definition of a foreign-data wrapper
"ALTER FOREIGN TABLE": true, // change the definition of a foreign table
"ALTER FUNCTION": true, // change the definition of a function
"ALTER GROUP": true, // change role name or membership
"ALTER INDEX": true, // change the definition of an index
"ALTER LANGUAGE": true, // change the definition of a procedural language
"ALTER LARGE OBJECT": true, // change the definition of a large object
"ALTER MATERIALIZED VIEW": true, // change the definition of a materialized view
"ALTER OPERATOR CLASS": true, // change the definition of an operator class
"ALTER OPERATOR FAMILY": true, // change the definition of an operator family
"ALTER OPERATOR": true, // change the definition of an operator
"ALTER POLICY": true, // change the definition of a row level security policy
"ALTER ROLE": true, // change a database role
"ALTER RULE": true, // change the definition of a rule
"ALTER SCHEMA": true, // change the definition of a schema
"ALTER SEQUENCE": true, // change the definition of a sequence generator
"ALTER SERVER": true, // change the definition of a foreign server
"ALTER SYSTEM": true, // change a server configuration parameter
"ALTER TABLESPACE": true, // change the definition of a tablespace
"ALTER TABLE": true, // change the definition of a table
"ALTER TEXT SEARCH CONFIGURATION": true, // change the definition of a text search configuration
"ALTER TEXT SEARCH DICTIONARY": true, // change the definition of a text search dictionary
"ALTER TEXT SEARCH PARSER": true, // change the definition of a text search parser
"ALTER TEXT SEARCH TEMPLATE": true, // change the definition of a text search template
"ALTER TRIGGER": true, // change the definition of a trigger
"ALTER TYPE": true, // change the definition of a type
"ALTER USER MAPPING": true, // change the definition of a user mapping
"ALTER USER": true, // change a database role
"ALTER VIEW": true, // change the definition of a view
"ANALYZE": true, // collect statistics about a database
"BEGIN": true, // start a transaction block
"CHECKPOINT": true, // force a transaction log checkpoint
"CLOSE": true, // close a cursor
"CLUSTER": true, // cluster a table according to an index
"COMMENT": true, // define or change the comment of an object
"COMMIT PREPARED": true, // commit a transaction that was earlier prepared for two-phase commit
"COMMIT": true, // commit the current transaction
"COPY": true, // copy data between a file and a table
"CREATE ACCESS METHOD": true, // define a new access method
"CREATE AGGREGATE": true, // define a new aggregate function
"CREATE CAST": true, // define a new cast
"CREATE COLLATION": true, // define a new collation
"CREATE CONVERSION": true, // define a new encoding conversion
"CREATE DATABASE": true, // create a new database
"CREATE DOMAIN": true, // define a new domain
"CREATE EVENT TRIGGER": true, // define a new event trigger
"CREATE EXTENSION": true, // install an extension
"CREATE FOREIGN DATA WRAPPER": true, // define a new foreign-data wrapper
"CREATE FOREIGN TABLE": true, // define a new foreign table
"CREATE FUNCTION": true, // define a new function
"CREATE GROUP": true, // define a new database role
"CREATE INDEX": true, // define a new index
"CREATE LANGUAGE": true, // define a new procedural language
"CREATE MATERIALIZED VIEW": true, // define a new materialized view
"CREATE OPERATOR CLASS": true, // define a new operator class
"CREATE OPERATOR FAMILY": true, // define a new operator family
"CREATE OPERATOR": true, // define a new operator
"CREATE POLICY": true, // define a new row level security policy for a table
"CREATE ROLE": true, // define a new database role
"CREATE RULE": true, // define a new rewrite rule
"CREATE SCHEMA": true, // define a new schema
"CREATE SEQUENCE": true, // define a new sequence generator
"CREATE SERVER": true, // define a new foreign server
"CREATE STATISTICS": true, // define extended statistics
"CREATE SUBSCRIPTION": true, // define a new subscription
"CREATE TABLE AS": true, // define a new table from the results of a query
"CREATE TABLESPACE": true, // define a new tablespace
"CREATE TABLE": true, // define a new table
"CREATE TEXT SEARCH CONFIGURATION": true, // define a new text search configuration
"CREATE TEXT SEARCH DICTIONARY": true, // define a new text search dictionary
"CREATE TEXT SEARCH PARSER": true, // define a new text search parser
"CREATE TEXT SEARCH TEMPLATE": true, // define a new text search template
"CREATE TRANSFORM": true, // define a new transform
"CREATE TRIGGER": true, // define a new trigger
"CREATE TYPE": true, // define a new data type
"CREATE USER MAPPING": true, // define a new mapping of a user to a foreign server
"CREATE USER": true, // define a new database role
"CREATE VIEW": true, // define a new view
"DEALLOCATE ALL": true, // deallocate all prepared statements
"DEALLOCATE": true, // deallocate a prepared statement
"DECLARE": true, // define a cursor
"DELETE": true, // delete rows of a table
"DISCARD": true, // discard session state
"DO": true, // execute an anonymous code block
"DROP ACCESS METHOD": true, // remove an access method
"DROP AGGREGATE": true, // remove an aggregate function
"DROP CAST": true, // remove a cast
"DROP COLLATION": true, // remove a collation
"DROP CONVERSION": true, // remove a conversion
"DROP DATABASE": true, // remove a database
"DROP DOMAIN": true, // remove a domain
"DROP EVENT TRIGGER": true, // remove an event trigger
"DROP EXTENSION": true, // remove an extension
"DROP FOREIGN DATA WRAPPER": true, // remove a foreign-data wrapper
"DROP FOREIGN TABLE": true, // remove a foreign table
"DROP FUNCTION": true, // remove a function
"DROP GROUP": true, // remove a database role
"DROP INDEX": true, // remove an index
"DROP LANGUAGE": true, // remove a procedural language
"DROP MATERIALIZED VIEW": true, // remove a materialized view
"DROP OPERATOR CLASS": true, // remove an operator class
"DROP OPERATOR FAMILY": true, // remove an operator family
"DROP OPERATOR": true, // remove an operator
"DROP OWNED": true, // remove database objects owned by a database role
"DROP POLICY": true, // remove a row level security policy from a table
"DROP PUBLICATION": true, // remove a publication
"DROP ROLE": true, // remove a database role
"DROP RULE": true, // remove a rewrite rule
"DROP SCHEMA": true, // remove a schema
"DROP SEQUENCE": true, // remove a sequence
"DROP SERVER": true, // remove a foreign server descriptor
"DROP STATISTICS": true, // remove extended statistics
"DROP SUBSCRIPTION": true, // remove a subscription
"DROP TABLESPACE": true, // remove a tablespace
"DROP TABLE": true, // remove a table
"DROP TEXT SEARCH CONFIGURATION": true, // remove a text search configuration
"DROP TEXT SEARCH DICTIONARY": true, // remove a text search dictionary
"DROP TEXT SEARCH PARSER": true, // remove a text search parser
"DROP TEXT SEARCH TEMPLATE": true, // remove a text search template
"DROP TRANSFORM": true, // remove a transform
"DROP TRIGGER": true, // remove a trigger
"DROP TYPE": true, // remove a data type
"DROP USER MAPPING": true, // remove a user mapping for a foreign server
"DROP USER": true, // remove a database role
"DROP VIEW": true, // remove a view
"END": true, // commit the current transaction
"EXECUTE": true, // execute a prepared statement
"GRANT": true, // define access privileges
"IMPORT FOREIGN SCHEMA": true, // import table definitions from a foreign server
"INSERT": true, // create new rows in a table
"LISTEN": true, // listen for a notification
"LOAD": true, // load a shared library file
"LOCK": true, // lock a table
"MOVE": true, // position a cursor
"NOTIFY": true, // generate a notification
"PREPARE TRANSACTION": true, // prepare the current transaction for two-phase commit
"PREPARE": true, // prepare a statement for execution
"REASSIGN OWNED": true, // change the ownership of database objects owned by a database role
"REFRESH MATERIALIZED VIEW": true, // replace the contents of a materialized view
"REINDEX": true, // rebuild indexes
"RELEASE": true, // destroy a previously defined savepoint
"RESET": true, // restore the value of a run-time parameter to the default value
"REVOKE": true, // remove access privileges
"ROLLBACK PREPARED": true, // cancel a transaction that was earlier prepared for two-phase commit
"ROLLBACK TO SAVEPOINT": true, // roll back to a savepoint
"ROLLBACK": true, // abort the current transaction
"SAVEPOINT": true, // define a new savepoint within the current transaction
"SECURITY LABEL": true, // define or change a security label applied to an object
"SELECT INTO": true, // define a new table from the results of a query
"SET CONSTRAINTS": true, // set constraint check timing for the current transaction
"SET ROLE": true, // set the current user identifier of the current session
"SET SESSION AUTHORIZATION": true, // set the session user identifier and the current user identifier of the current session
"SET TRANSACTION": true, // set the characteristics of the current transaction
"SET": true, // change a run-time parameter
"START TRANSACTION": true, // start a transaction block
"TRUNCATE": true, // empty a table or set of tables
"UNLISTEN": true, // stop listening for a notification
"UPDATE": true, // update rows of a table
"VACUUM": true, // garbage-collect and optionally analyze a database
// oracle
"ADMINISTER KEY MANAGEMENT": true,
"ALTER ANALYTIC VIEW": true,
"ALTER ATTRIBUTE DIMENSION": true,
"ALTER AUDIT POLICY": true,
"ALTER CLUSTER": true,
"ALTER DATABASE DICTIONARY": true,
"ALTER DATABASE LINK": true,
"ALTER DIMENSION": true,
"ALTER DISKGROUP": true,
"ALTER FLASHBACK ARCHIVE": true,
"ALTER HEIRARCHY": true,
"ALTER INMEMORY JOIN GROUP": true,
"ALTER JAVA": true,
"ALTER LIBRARY": true,
"ALTER LOCKDOWN PROFILE": true,
"ALTER MATERIALIZED VIEW LOG": true,
"ALTER MATERIALIZED ZONEMAP": true,
"ALTER PACKAGE": true,
"ALTER PLUGGABLE DATABASE": true,
"ALTER PROCEDURE": true,
"ALTER PROFILE": true,
"ALTER RESOURCE COST": true,
"ALTER ROLLBACK SEGMENT": true,
"ALTER SESSION": true,
"ALTER SYNONYM": true,
"ALTER TABLESPACE SET": true,
"ASSOCIATE STATISTICS": true,
}
// createIgnore are parts of the query exec type after CREATE to ignore.
var createIgnore = map[string]bool{
"DEFAULT": true,
"GLOBAL": true,
"LOCAL": true,
"OR": true,
"PROCEDURAL": true,
"RECURSIVE": true,
"REPLACE": true,
"TEMPORARY": true,
"TEMP": true,
"TRUSTED": true,
"UNIQUE": true,
"UNLOGGED": true,
}
// QueryExecType is the default way to determine the "EXEC" prefix for a SQL
// query and whether or not it should be Exec'd or Query'd.
func QueryExecType(prefix, sqlstr string) (string, bool) {
if prefix == "" {
return "EXEC", false
}
s := strings.Split(prefix, " ")
if len(s) > 0 {
// check query map
if _, ok := queryMap[s[0]]; ok {
typ := s[0]
switch {
case typ == "SELECT" && len(s) >= 2 && s[1] == "INTO":
return "SELECT INTO", false
case typ == "PRAGMA":
return typ, !strings.ContainsRune(sqlstr, '=')
}
return typ, true
}
// normalize prefixes
switch s[0] {
// CREATE statements have a large number of variants
case "CREATE":
n := []string{"CREATE"}
for _, x := range s[1:] {
if _, ok := createIgnore[x]; ok {
continue
}
n = append(n, x)
}
s = n
case "DROP":
// "DROP [PROCEDURAL] LANGUAGE" => "DROP LANGUAGE"
n := []string{"DROP"}
for _, x := range s[1:] {
if x == "PROCEDURAL" {
continue
}
n = append(n, x)
}
s = n
}
// find longest match
for i := len(s); i > 0; i-- {
typ := strings.Join(s[:i], " ")
if _, ok := execMap[typ]; ok {
return typ, false
}
}
}
return s[0], false
}
================================================
FILE: drivers/ramsql/ramsql.go
================================================
// Package ramsql defines and registers usql's RamSQL driver.
//
// See: https://github.com/proullon/ramsql
package ql
import (
_ "github.com/proullon/ramsql/driver" // DRIVER
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("ramsql", drivers.Driver{})
}
================================================
FILE: drivers/sapase/sapase.go
================================================
// Package sapase defines and registers usql's SAP ASE driver.
//
// See: https://github.com/thda/tds
package sapase
import (
"context"
"errors"
"strconv"
"strings"
"github.com/thda/tds" // DRIVER: tds
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("tds", drivers.Driver{
AllowMultilineComments: true,
RequirePreviousPassword: true,
LexerName: "tsql",
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
err := db.QueryRowContext(ctx, `SELECT @@version`).Scan(&ver)
if err != nil {
return "", err
}
return ver, nil
},
ChangePassword: func(db drivers.DB, user, newpw, oldpw string) error {
if user != "" {
return errors.New("Cannot change password for another user")
}
_, err := db.Exec(`exec sp_password '` + oldpw + `', '` + newpw + `'`)
return err
},
Err: func(err error) (string, string) {
if e, ok := err.(tds.SybError); ok {
return strconv.Itoa(int(e.MsgNumber)), e.Message
}
msg := err.Error()
if i := strings.LastIndex(msg, "tds:"); i != -1 {
msg = msg[i:]
}
return "", msg
},
IsPasswordErr: func(err error) bool {
return strings.Contains(err.Error(), "Login failed")
},
Process: drivers.StripTrailingSemicolon,
})
}
================================================
FILE: drivers/saphana/saphana.go
================================================
// Package saphana defines and registers usql's SAP HANA driver.
//
// See: https://github.com/SAP/go-hdb
package saphana
import (
"context"
"strconv"
_ "github.com/SAP/go-hdb/driver" // DRIVER: hdb
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("hdb", drivers.Driver{
AllowMultilineComments: true,
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
if err := db.QueryRowContext(ctx, `SELECT version FROM m_database`).Scan(&ver); err != nil {
return "", err
}
return "SAP HANA " + ver, nil
},
Err: func(err error) (string, string) {
code, msg := "", err.Error()
if e, ok := err.(interface {
Code() int
}); ok {
code = strconv.Itoa(e.Code())
}
if e, ok := err.(interface {
Text() string
}); ok {
msg = e.Text()
}
return code, msg
},
})
}
================================================
FILE: drivers/snowflake/snowflake.go
================================================
// Package snowflake defines and registers usql's Snowflake driver.
//
// See: https://github.com/snowflakedb/gosnowflake
package snowflake
import (
"io"
"strconv"
"github.com/snowflakedb/gosnowflake" // DRIVER
"github.com/xo/tblfmt"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
infos "github.com/xo/usql/drivers/metadata/informationschema"
"github.com/xo/usql/env"
)
func init() {
gosnowflake.GetLogger().SetOutput(io.Discard)
newReader := infos.New(
infos.WithPlaceholder(func(int) string { return "?" }),
infos.WithCustomClauses(map[infos.ClauseName]string{
infos.SequenceColumnsIncrement: "''",
}),
infos.WithFunctions(false),
infos.WithIndexes(false),
infos.WithConstraints(false),
infos.WithColumnPrivileges(false),
)
drivers.Register("snowflake", drivers.Driver{
AllowMultilineComments: true,
Err: func(err error) (string, string) {
if e, ok := err.(*gosnowflake.SnowflakeError); ok {
return strconv.Itoa(e.Number), e.Message
}
return "", err.Error()
},
NewMetadataReader: newReader,
NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer {
writerOpts := []metadata.WriterOption{
metadata.WithListAllDbs(func(pattern string, verbose bool) error {
return listAllDbs(db, w, pattern, verbose)
}),
}
return metadata.NewDefaultWriter(newReader(db, opts...), writerOpts...)(db, w)
},
})
}
func listAllDbs(db drivers.DB, w io.Writer, _ string, _ bool) error {
rows, err := db.Query("SHOW databases")
if err != nil {
return err
}
defer rows.Close()
params := env.Vars().Print()
params["title"] = "List of databases"
return tblfmt.EncodeAll(w, rows, params)
}
================================================
FILE: drivers/spanner/spanner.go
================================================
// Package spanner defines and registers usql's Google Spanner driver.
//
// See: https://github.com/googleapis/go-sql-spanner
package spanner
import (
_ "github.com/googleapis/go-sql-spanner" // DRIVER
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("spanner", drivers.Driver{})
}
================================================
FILE: drivers/sqlite3/sqlite3.go
================================================
// Package sqlite3 defines and registers usql's SQLite3 driver. Requires CGO.
//
// See: https://github.com/mattn/go-sqlite3
// Group: base
package sqlite3
import (
"context"
"strconv"
"github.com/mattn/go-sqlite3" // DRIVER
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/sqlite3/sqshared"
)
func init() {
drivers.Register("sqlite3", drivers.Driver{
AllowMultilineComments: true,
ForceParams: drivers.ForceQueryParameters([]string{
"loc", "auto",
}),
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
err := db.QueryRowContext(ctx, `SELECT sqlite_version()`).Scan(&ver)
if err != nil {
return "", err
}
return "SQLite3 " + ver, nil
},
Err: func(err error) (string, string) {
if e, ok := err.(sqlite3.Error); ok {
return strconv.Itoa(int(e.Code)), e.Error()
}
code, msg := "", err.Error()
if e, ok := err.(sqlite3.ErrNo); ok {
code = strconv.Itoa(int(e))
}
return code, msg
},
ConvertBytes: sqshared.ConvertBytes,
NewMetadataReader: sqshared.NewMetadataReader,
Copy: drivers.CopyWithInsert(func(int) string { return "?" }),
})
}
================================================
FILE: drivers/sqlite3/sqshared/reader.go
================================================
package sqshared
import (
"database/sql"
"fmt"
"strings"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
)
type MetadataReader struct {
metadata.LoggingReader
limit int
}
// NewMetadataReader creates the metadata reader for sqlite3 databases.
func NewMetadataReader(db drivers.DB, opts ...metadata.ReaderOption) metadata.Reader {
return &MetadataReader{
LoggingReader: metadata.NewLoggingReader(db, opts...),
}
}
var (
_ metadata.BasicReader = &MetadataReader{}
_ metadata.FunctionReader = &MetadataReader{}
_ metadata.FunctionColumnReader = &MetadataReader{}
_ metadata.IndexReader = &MetadataReader{}
_ metadata.IndexColumnReader = &MetadataReader{}
)
func (r *MetadataReader) SetLimit(l int) {
r.limit = l
}
// Columns from selected catalog (or all, if empty), matching schemas and tables
func (r MetadataReader) Columns(f metadata.Filter) (*metadata.ColumnSet, error) {
tables, err := r.Tables(metadata.Filter{Catalog: f.Catalog, Schema: f.Schema, Name: f.Parent})
if err != nil {
return nil, err
}
results := []metadata.Column{}
for tables.Next() {
table := tables.Get()
qstr := `SELECT
cid,
name,
type,
CASE WHEN "notnull" = 1 THEN 'NO' ELSE 'YES' END,
COALESCE(dflt_value, '')
FROM pragma_table_info(?)`
rows, closeRows, err := r.query(qstr, []string{}, "name", table.Name)
if err != nil {
return nil, err
}
defer closeRows()
rec := metadata.Column{
Catalog: table.Catalog,
Schema: table.Schema,
Table: table.Name,
}
for rows.Next() {
err = rows.Scan(
&rec.OrdinalPosition,
&rec.Name,
&rec.DataType,
&rec.IsNullable,
&rec.Default,
)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
}
return metadata.NewColumnSet(results), nil
}
func (r MetadataReader) Tables(f metadata.Filter) (*metadata.TableSet, error) {
qstr := `SELECT
'' AS table_catalog,
'' AS table_schem,
table_name,
table_type
FROM (
SELECT
name AS table_name,
UPPER(type) AS table_type
FROM sqlite_master
WHERE name NOT LIKE 'sqlite\_%' ESCAPE '\' AND UPPER(type) IN ('TABLE', 'VIEW')
UNION ALL
SELECT
name AS table_name,
'GLOBAL TEMPORARY' AS table_type
FROM sqlite_temp_master
UNION ALL
SELECT
name AS table_name,
'SYSTEM TABLE' AS table_type
FROM sqlite_master
WHERE name LIKE 'sqlite\_%' ESCAPE '\' AND UPPER(type) IN ('TABLE', 'VIEW')
UNION ALL
SELECT
name AS table_name,
'SYSTEM TABLE' AS table_type
FROM pragma_module_list
)`
conds := []string{}
vals := []interface{}{}
if f.Catalog != "" {
vals = append(vals, f.Catalog)
conds = append(conds, "table_catalog = ?")
}
if f.Schema != "" {
vals = append(vals, f.Schema)
conds = append(conds, "table_schema LIKE ?")
}
if f.Name != "" {
vals = append(vals, f.Name)
conds = append(conds, "table_name LIKE ?")
}
if len(f.Types) != 0 {
pholders := []string{}
for _, t := range f.Types {
vals = append(vals, t)
pholders = append(pholders, "?")
}
if len(pholders) != 0 {
conds = append(conds, "table_type IN ("+strings.Join(pholders, ", ")+")")
}
}
rows, closeRows, err := r.query(qstr, conds, "table_type, table_name", vals...)
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.Table{}
for rows.Next() {
rec := metadata.Table{}
err = rows.Scan(&rec.Catalog, &rec.Schema, &rec.Name, &rec.Type)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewTableSet(results), nil
}
func (r MetadataReader) Schemas(f metadata.Filter) (*metadata.SchemaSet, error) {
qstr := `SELECT
name AS schema_name,
'' AS catalog_name
FROM pragma_database_list`
conds := []string{}
vals := []interface{}{}
if f.Name != "" {
vals = append(vals, f.Name)
conds = append(conds, "schema_name LIKE ?")
}
rows, closeRows, err := r.query(qstr, conds, "seq", vals...)
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.Schema{}
for rows.Next() {
rec := metadata.Schema{}
err = rows.Scan(&rec.Schema, &rec.Catalog)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewSchemaSet(results), nil
}
func (r MetadataReader) Functions(f metadata.Filter) (*metadata.FunctionSet, error) {
qstr := `SELECT
name AS specific_name,
name AS routine_name,
type AS routine_type
FROM pragma_function_list`
conds := []string{}
vals := []interface{}{}
if f.Name != "" {
vals = append(vals, f.Name)
conds = append(conds, "name LIKE ?")
}
if len(f.Types) != 0 {
pholders := []string{}
for _, t := range f.Types {
vals = append(vals, t)
pholders = append(pholders, "?")
}
if len(pholders) != 0 {
conds = append(conds, "type IN ("+strings.Join(pholders, ", ")+")")
}
}
rows, closeRows, err := r.query(qstr, conds, "name, type", vals...)
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.Function{}
for rows.Next() {
rec := metadata.Function{}
err = rows.Scan(
&rec.SpecificName,
&rec.Name,
&rec.Type,
)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewFunctionSet(results), nil
}
func (r MetadataReader) FunctionColumns(metadata.Filter) (*metadata.FunctionColumnSet, error) {
return &metadata.FunctionColumnSet{}, nil
}
func (r MetadataReader) Indexes(f metadata.Filter) (*metadata.IndexSet, error) {
qstr := `SELECT
m.name,
i.name,
CASE WHEN i."unique" = 1 THEN 'YES' ELSE 'NO' END,
CASE WHEN i.origin = 'pk' THEN 'YES' ELSE 'NO' END
FROM sqlite_master m
JOIN pragma_index_list(m.name) i`
conds := []string{"m.type = 'table'"}
vals := []interface{}{}
if f.Parent != "" {
vals = append(vals, f.Parent)
conds = append(conds, "m.name LIKE ?")
}
if f.Name != "" {
vals = append(vals, f.Name)
conds = append(conds, "i.name LIKE ?")
}
rows, closeRows, err := r.query(qstr, conds, "m.name, i.seq", vals...)
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.Index{}
for rows.Next() {
rec := metadata.Index{}
err = rows.Scan(&rec.Table, &rec.Name, &rec.IsUnique, &rec.IsPrimary)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewIndexSet(results), nil
}
func (r MetadataReader) IndexColumns(f metadata.Filter) (*metadata.IndexColumnSet, error) {
qstr := `SELECT
m.name,
i.name,
ic.name,
ic.seqno
FROM sqlite_master m
JOIN pragma_index_list(m.name) i
JOIN pragma_index_xinfo(i.name) ic`
conds := []string{"m.type = 'table' AND ic.cid >= 0"}
vals := []interface{}{}
if f.Parent != "" {
vals = append(vals, f.Parent)
conds = append(conds, "m.name LIKE ?")
}
if f.Name != "" {
vals = append(vals, f.Name)
conds = append(conds, "i.name LIKE ?")
}
rows, closeRows, err := r.query(qstr, conds, "m.name, i.seq, ic.seqno", vals...)
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.IndexColumn{}
for rows.Next() {
rec := metadata.IndexColumn{}
err = rows.Scan(&rec.Table, &rec.IndexName, &rec.Name, &rec.OrdinalPosition)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewIndexColumnSet(results), nil
}
func (r MetadataReader) query(qstr string, conds []string, order string, vals ...interface{}) (*sql.Rows, func(), error) {
if len(conds) != 0 {
qstr += "\nWHERE " + strings.Join(conds, " AND ")
}
if order != "" {
qstr += "\nORDER BY " + order
}
if r.limit != 0 {
qstr += fmt.Sprintf("\nLIMIT %d", r.limit)
}
return r.Query(qstr, vals...)
}
================================================
FILE: drivers/sqlite3/sqshared/reader_test.go
================================================
package sqshared
import (
"bufio"
"context"
"database/sql"
"fmt"
"log"
"os"
"os/user"
"path"
"strings"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/archive"
"github.com/xo/usql/drivers/metadata"
)
var (
db *sql.DB
reader *MetadataReader
)
func TestMain(m *testing.M) {
err := createDb("testdata", "sakila.db")
if err != nil {
log.Fatalf("Could not prepare the database: %s", err)
}
db, err = sql.Open("sqlite3", "testdata/sakila.db")
if err != nil {
log.Fatalf("Could not open the database: %s", err)
}
reader = &MetadataReader{LoggingReader: metadata.NewLoggingReader(db)}
code := m.Run()
os.Exit(code)
}
func createDb(location, name string) error {
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return err
}
tar, err := archive.TarWithOptions("../metadata/testdata/docker", &archive.TarOptions{})
if err != nil {
return err
}
baseImage := "centos:7"
schemaURL := "https://raw.githubusercontent.com/jOOQ/sakila/main/sqlite-sakila-db/sqlite-sakila-schema.sql"
target := "/schema"
buildOptions := types.ImageBuildOptions{
Tags: []string{"usql-sqlite"},
BuildArgs: map[string]*string{
"BASE_IMAGE": &baseImage,
"SCHEMA_URL": &schemaURL,
"TARGET": &target,
},
}
res, err := cli.ImageBuild(ctx, tar, buildOptions)
if err != nil {
return err
}
defer res.Body.Close()
scanner := bufio.NewScanner(res.Body)
for scanner.Scan() {
}
cwd, err := os.Getwd()
if err != nil {
return err
}
u, err := user.Current()
if err != nil {
return err
}
resp, err := cli.ContainerCreate(ctx, &container.Config{
Image: "usql-sqlite",
Cmd: []string{"bash", "-xc", "sqlite3 -batch -echo -init /schema/sqlite-sakila-schema.sql /data/" + name},
User: u.Uid + ":" + u.Gid,
NetworkDisabled: true,
}, &container.HostConfig{
Binds: []string{
path.Join(cwd, location) + ":/data",
},
}, nil, nil, "")
if err != nil {
return err
}
err = cli.ContainerStart(ctx, resp.ID, container.StartOptions{})
if err != nil {
return err
}
statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
select {
case err := <-errCh:
if err != nil {
return err
}
case status := <-statusCh:
fmt.Println(status.StatusCode, status.Error)
}
//out, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true})
//if err != nil {
// return err
//}
//_, err = stdcopy.StdCopy(os.Stdout, os.Stderr, out)
//if err != nil {
// return err
//}
return cli.ContainerRemove(ctx, resp.ID, container.RemoveOptions{})
}
func TestSchemas(t *testing.T) {
result, err := reader.Schemas(metadata.Filter{})
if err != nil {
log.Fatalf("Could not read schemas: %v", err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Schema)
}
actual := strings.Join(names, ", ")
expected := "main"
if actual != expected {
t.Errorf("Wrong schema names, expected:\n %v\ngot:\n %v", expected, names)
}
}
func TestTables(t *testing.T) {
result, err := reader.Tables(metadata.Filter{Types: []string{"BASE TABLE", "TABLE", "VIEW"}})
if err != nil {
log.Fatalf("Could not read tables: %v", err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Name)
}
actual := strings.Join(names, ", ")
expected := "actor, address, category, city, country, customer, film, film_actor, film_category, film_text, inventory, language, payment, rental, staff, store, customer_list, film_list, sales_by_film_category, sales_by_store, staff_list"
if actual != expected {
t.Errorf("Wrong table names, expected:\n %v\ngot:\n %v", expected, names)
}
}
func TestColumns(t *testing.T) {
result, err := reader.Columns(metadata.Filter{Parent: "film%"})
if err != nil {
log.Fatalf("Could not read columns: %v", err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Name)
}
actual := strings.Join(names, ", ")
expected := "description, film_id, language_id, last_update, length, original_language_id, rating, release_year, rental_duration, rental_rate, replacement_cost, special_features, title, actor_id, film_id, last_update, category_id, film_id, last_update, description, film_id, title, FID, actors, category, description, length, price, rating, title"
if actual != expected {
t.Errorf("Wrong column names, expected:\n %v, got:\n %v", expected, names)
}
}
func TestFunctions(t *testing.T) {
result, err := reader.Functions(metadata.Filter{})
if err != nil {
log.Fatalf("Could not read functions: %v", err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Name)
}
actual := strings.Join(names, ", ")
expected := "abs, auth_enabled, auth_user_add, auth_user_change, auth_user_delete, authenticate, avg, changes, char, coalesce, count, count, cume_dist, current_date, current_time, current_timestamp, date, datetime, dense_rank, first_value, fts3_tokenizer, fts3_tokenizer, glob, group_concat, group_concat, hex, ifnull, instr, julianday, lag, lag, lag, last_insert_rowid, last_value, lead, lead, lead, length, like, like, likelihood, likely, load_extension, load_extension, lower, ltrim, ltrim, match, matchinfo, matchinfo, max, max, min, min, nth_value, ntile, nullif, offsets, optimize, percent_rank, printf, quote, random, randomblob, rank, replace, round, round, row_number, rtreecheck, rtreedepth, rtreenode, rtrim, rtrim, snippet, sqlite_compileoption_get, sqlite_compileoption_used, sqlite_log, sqlite_source_id, sqlite_version, strftime, substr, substr, sum, time, total, total_changes, trim, trim, typeof, unicode, unlikely, upper, zeroblob"
if actual != expected {
t.Errorf("Wrong function names, expected:\n %v\ngot:\n %v", expected, names)
}
}
func TestIndexes(t *testing.T) {
result, err := reader.Indexes(metadata.Filter{})
if err != nil {
log.Fatalf("Could not read indexes: %v", err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Table+"."+result.Get().Name)
}
actual := strings.Join(names, ", ")
expected := "actor.idx_actor_last_name, actor.sqlite_autoindex_actor_1, address.idx_fk_city_id, address.sqlite_autoindex_address_1, category.sqlite_autoindex_category_1, city.idx_fk_country_id, city.sqlite_autoindex_city_1, country.sqlite_autoindex_country_1, customer.idx_customer_last_name, customer.idx_customer_fk_address_id, customer.idx_customer_fk_store_id, customer.sqlite_autoindex_customer_1, film.idx_fk_original_language_id, film.idx_fk_language_id, film.sqlite_autoindex_film_1, film_actor.idx_fk_film_actor_actor, film_actor.idx_fk_film_actor_film, film_actor.sqlite_autoindex_film_actor_1, film_category.idx_fk_film_category_category, film_category.idx_fk_film_category_film, film_category.sqlite_autoindex_film_category_1, film_text.sqlite_autoindex_film_text_1, inventory.idx_fk_film_id_store_id, inventory.idx_fk_film_id, inventory.sqlite_autoindex_inventory_1, language.sqlite_autoindex_language_1, payment.idx_fk_customer_id, payment.idx_fk_staff_id, payment.sqlite_autoindex_payment_1, rental.idx_rental_uq, rental.idx_rental_fk_staff_id, rental.idx_rental_fk_customer_id, rental.idx_rental_fk_inventory_id, rental.sqlite_autoindex_rental_1, staff.idx_fk_staff_address_id, staff.idx_fk_staff_store_id, staff.sqlite_autoindex_staff_1, store.idx_fk_store_address, store.idx_store_fk_manager_staff_id, store.sqlite_autoindex_store_1"
if actual != expected {
t.Errorf("Wrong index names, expected:\n %v\ngot:\n %v", expected, names)
}
}
func TestIndexColumns(t *testing.T) {
result, err := reader.IndexColumns(metadata.Filter{Name: "idx%"})
if err != nil {
log.Fatalf("Could not read index columns: %v", err)
}
names := []string{}
for result.Next() {
names = append(names, result.Get().Name)
}
actual := strings.Join(names, ", ")
expected := "last_name, city_id, country_id, last_name, address_id, store_id, original_language_id, language_id, actor_id, film_id, category_id, film_id, store_id, film_id, film_id, customer_id, staff_id, rental_date, inventory_id, customer_id, staff_id, customer_id, inventory_id, address_id, store_id, address_id, manager_staff_id"
if actual != expected {
t.Errorf("Wrong index column names, expected:\n %v, got:\n %v", expected, names)
}
}
================================================
FILE: drivers/sqlite3/sqshared/sqshared.go
================================================
// Package sqshared contains shared types for the sqlite3 and moderncsqlite
// drivers.
package sqshared
import (
"database/sql/driver"
"errors"
"fmt"
"strings"
"time"
)
// ConvertBytes is the byte formatter func for sqlite3 databases.
func ConvertBytes(buf []byte, tfmt string) (string, error) {
// attempt to convert buf if it matches a time format, and if it
// does, then return a formatted time string.
s := string(buf)
if s != "" && strings.TrimSpace(s) != "" {
t := new(Time)
if err := t.Scan(buf); err == nil {
return time.Time(*t).Format(tfmt), nil
}
}
return s, nil
}
// Time provides a type that will correctly scan the various timestamps
// values stored by the github.com/mattn/go-sqlite3 driver for time.Time
// values, as well as correctly satisfying the sql/driver/Valuer interface.
type Time time.Time
// Value satisfies the Valuer interface.
func (t *Time) Value() (driver.Value, error) {
return t, nil
}
// Scan satisfies the Scanner interface.
func (t *Time) Scan(v interface{}) error {
switch x := v.(type) {
case time.Time:
*t = Time(x)
return nil
case []byte:
return t.Parse(string(x))
case string:
return t.Parse(x)
}
return fmt.Errorf("cannot convert type %T to Time", v)
}
// Parse attempts to Parse string s to t.
func (t *Time) Parse(s string) error {
if s == "" {
return nil
}
for _, f := range SQLiteTimestampFormats {
if z, err := time.Parse(f, s); err == nil {
*t = Time(z)
return nil
}
}
return errors.New("could not parse time")
}
// SQLiteTimestampFormats is timestamp formats understood by both this module
// and SQLite. The first format in the slice will be used when saving time
// values into the database. When parsing a string from a timestamp or datetime
// column, the formats are tried in order.
var SQLiteTimestampFormats = []string{
// By default, store timestamps with whatever timezone they come with.
// When parsed, they will be returned with the same timezone.
"2006-01-02 15:04:05.999999999-07:00",
"2006-01-02T15:04:05.999999999-07:00",
"2006-01-02 15:04:05.999999999",
"2006-01-02T15:04:05.999999999",
"2006-01-02 15:04:05",
"2006-01-02T15:04:05",
"2006-01-02 15:04",
"2006-01-02T15:04",
"2006-01-02",
}
================================================
FILE: drivers/sqlserver/reader.go
================================================
package sqlserver
import (
"database/sql"
"fmt"
"strings"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
infos "github.com/xo/usql/drivers/metadata/informationschema"
)
type metaReader struct {
metadata.LoggingReader
limit int
}
var _ metadata.CatalogReader = &metaReader{}
var _ metadata.IndexReader = &metaReader{}
var _ metadata.IndexColumnReader = &metaReader{}
func NewReader(db drivers.DB, opts ...metadata.ReaderOption) metadata.Reader {
ir := infos.New(
infos.WithPlaceholder(placeholder),
infos.WithIndexes(false),
infos.WithSequences(false),
infos.WithConstraints(false),
infos.WithCustomClauses(map[infos.ClauseName]string{
infos.FunctionsSecurityType: "''",
}),
infos.WithSystemSchemas([]string{
"db_accessadmin",
"db_backupoperator",
"db_datareader",
"db_datawriter",
"db_ddladmin",
"db_denydatareader",
"db_denydatawriter",
"db_owner",
"db_securityadmin",
"INFORMATION_SCHEMA",
"sys",
}),
infos.WithCurrentSchema("schema_name()"),
infos.WithDataTypeFormatter(dataTypeFormatter),
infos.WithUsagePrivileges(false),
)(db, opts...)
mr := &metaReader{
LoggingReader: metadata.NewLoggingReader(db, opts...),
}
return metadata.NewPluginReader(ir, mr)
}
func dataTypeFormatter(col metadata.Column) string {
switch col.DataType {
case "numeric", "decimal":
if col.ColumnSize == 18 && col.DecimalDigits == 0 {
return col.DataType
} else {
return fmt.Sprintf("%s(%d,%d)", col.DataType, col.ColumnSize, col.DecimalDigits)
}
case "datetimeoffset", "datetime2", "time":
if col.ColumnSize == 7 {
return col.DataType
} else {
return fmt.Sprintf("%s(%d)", col.DataType, col.ColumnSize)
}
case "char", "nchar", "binary":
if col.ColumnSize == 1 {
return col.DataType
} else {
return fmt.Sprintf("%s(%d)", col.DataType, col.ColumnSize)
}
case "varchar", "nvarchar", "varbinary":
if col.ColumnSize == -1 {
return col.DataType + "(max)"
} else if col.ColumnSize == 1 {
return col.DataType
} else {
return fmt.Sprintf("%s(%d)", col.DataType, col.ColumnSize)
}
default:
return col.DataType
}
}
func (r *metaReader) SetLimit(l int) {
r.limit = l
}
func (r metaReader) Catalogs(metadata.Filter) (*metadata.CatalogSet, error) {
qstr := `SELECT name
FROM sys.databases`
rows, closeRows, err := r.query(qstr, []string{}, "name")
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.Catalog{}
for rows.Next() {
rec := metadata.Catalog{}
err = rows.Scan(&rec.Catalog)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewCatalogSet(results), nil
}
func (r metaReader) Indexes(f metadata.Filter) (*metadata.IndexSet, error) {
qstr := `
SELECT
db_name(),
s.name,
t.name,
COALESCE(i.name, ''),
CASE WHEN i.is_primary_key = 1 THEN 'YES' ELSE 'NO' END,
CASE WHEN i.is_unique = 1 THEN 'YES' ELSE 'NO' END,
i.type_desc
FROM sys.schemas s
JOIN sys.tables t on t.schema_id = s.schema_id
JOIN sys.indexes i ON i.object_id = t.object_id
`
conds := []string{}
vals := []interface{}{}
if f.OnlyVisible {
conds = append(conds, "s.name = schema_name()")
}
if !f.WithSystem {
conds = append(conds, "s.name NOT IN ('db_accessadmin', 'db_backupoperator', 'db_datareader', 'db_datawriter', 'db_ddladmin', 'db_denydatareader', 'db_denydatawriter', 'db_owner', 'db_securityadmin', 'INFORMATION_SCHEMA', 'sys')")
}
if f.Schema != "" {
vals = append(vals, f.Schema)
conds = append(conds, fmt.Sprintf("s.name LIKE @p%d", len(vals)))
}
if f.Parent != "" {
vals = append(vals, f.Parent)
conds = append(conds, fmt.Sprintf("t.name LIKE @p%d", len(vals)))
}
if f.Name != "" {
vals = append(vals, f.Name)
conds = append(conds, fmt.Sprintf("i.name LIKE @p%d", len(vals)))
}
rows, closeRows, err := r.query(qstr, conds, "s.name, t.name, i.name", vals...)
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.Index{}
for rows.Next() {
rec := metadata.Index{}
err = rows.Scan(&rec.Catalog, &rec.Schema, &rec.Table, &rec.Name, &rec.IsUnique, &rec.IsPrimary, &rec.Type)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewIndexSet(results), nil
}
func (r metaReader) IndexColumns(f metadata.Filter) (*metadata.IndexColumnSet, error) {
qstr := `
SELECT
db_name(),
s.name,
t.name,
COALESCE(i.name, ''),
c.name,
t.name,
ic.key_ordinal
FROM sys.schemas s
JOIN sys.tables t on t.schema_id = s.schema_id
JOIN sys.indexes i ON i.object_id = t.object_id
JOIN sys.index_columns ic ON i.object_id = ic.object_id and i.index_id = ic.index_id
JOIN sys.columns c ON ic.object_id = c.object_id and ic.column_id = c.column_id
JOIN sys.types ty ON ty.user_type_id = c.user_type_id
`
conds := []string{}
vals := []interface{}{}
if f.OnlyVisible {
conds = append(conds, "s.name = schema_name()")
}
if !f.WithSystem {
conds = append(conds, "s.name NOT IN ('db_accessadmin', 'db_backupoperator', 'db_datareader', 'db_datawriter', 'db_ddladmin', 'db_denydatareader', 'db_denydatawriter', 'db_owner', 'db_securityadmin', 'INFORMATION_SCHEMA', 'sys')")
}
if f.Schema != "" {
vals = append(vals, f.Schema)
conds = append(conds, fmt.Sprintf("s.name LIKE @p%d", len(vals)))
}
if f.Parent != "" {
vals = append(vals, f.Parent)
conds = append(conds, fmt.Sprintf("t.name LIKE @p%d", len(vals)))
}
if f.Name != "" {
vals = append(vals, f.Name)
conds = append(conds, fmt.Sprintf("i.name LIKE @p%d", len(vals)))
}
rows, closeRows, err := r.query(qstr, conds, "s.name, t.name, i.name, ic.index_column_id", vals...)
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.IndexColumn{}
for rows.Next() {
rec := metadata.IndexColumn{}
err = rows.Scan(&rec.Catalog, &rec.Schema, &rec.Table, &rec.IndexName, &rec.Name, &rec.DataType, &rec.OrdinalPosition)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewIndexColumnSet(results), nil
}
func (r metaReader) query(qstr string, conds []string, order string, vals ...interface{}) (*sql.Rows, func(), error) {
if len(conds) != 0 {
qstr += "\nWHERE " + strings.Join(conds, " AND ")
}
if order != "" {
qstr += "\nORDER BY " + order
}
if r.limit != 0 {
qstr += fmt.Sprintf("\nFETCH FIRST %d ROWS ONLY", r.limit)
}
return r.Query(qstr, vals...)
}
================================================
FILE: drivers/sqlserver/sqlserver.go
================================================
// Package sqlserver defines and registers usql's Microsoft SQL Server driver.
//
// See: https://github.com/microsoft/go-mssqldb
// Group: base
package sqlserver
import (
"context"
"database/sql"
"fmt"
"io"
"strconv"
"strings"
mssql "github.com/microsoft/go-mssqldb"
sqlserver "github.com/microsoft/go-mssqldb" // DRIVER
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
// needed for azuresql authentication, named pipes, and shared memory transport protocols
_ "github.com/microsoft/go-mssqldb/azuread"
_ "github.com/microsoft/go-mssqldb/namedpipe"
_ "github.com/microsoft/go-mssqldb/sharedmemory"
)
func init() {
drivers.Register("sqlserver", drivers.Driver{
AllowMultilineComments: true,
RequirePreviousPassword: true,
LexerName: "tsql",
/*
// NOTE: this has been commented out, as it is not necessary. if
// NOTE: the azuread.DriverName is changed from `azuresql`, then
// NOTE: this func will be necessary as dburl will never import non
// NOTE: stdlib package. as is, dburl.Open will handle the call
// NOTE: to sql.Open and will pass the `azuresql` driver name
Open: func(_ context.Context, u *dburl.URL, _, _ func() io.Writer) (func(string, string) (*sql.DB, error), error) {
return func(_ string, params string) (*sql.DB, error) {
driver := "sqlserver"
switch {
case u.Query().Has("fedauth"),
strings.Contains(strings.ToLower(u.OriginalScheme), "azuresql"):
driver = azuread.DriverName
}
return sql.Open(driver, params)
}, nil
},
*/
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver, level, edition string
err := db.QueryRowContext(
ctx,
`SELECT SERVERPROPERTY('productversion'), SERVERPROPERTY ('productlevel'), SERVERPROPERTY ('edition')`,
).Scan(&ver, &level, &edition)
if err != nil {
return "", err
}
return "Microsoft SQL Server " + ver + ", " + level + ", " + edition, nil
},
ChangePassword: func(db drivers.DB, user, newpw, oldpw string) error {
_, err := db.Exec(`ALTER LOGIN ` + user + ` WITH password = '` + newpw + `' old_password = '` + oldpw + `'`)
return err
},
ColumnTypes: func(col *sql.ColumnType) (interface{}, error) {
switch col.DatabaseTypeName() {
case "UNIQUEIDENTIFIER":
if nullable, ok := col.Nullable(); ok && nullable {
return new(NullUniqueIdentifier), nil
}
return new(mssql.UniqueIdentifier), nil
}
return new(interface{}), nil
},
Err: func(err error) (string, string) {
if e, ok := err.(sqlserver.Error); ok {
return strconv.Itoa(int(e.Number)), e.Message
}
msg := err.Error()
if i := strings.LastIndex(msg, "sqlserver:"); i != -1 {
msg = msg[i:]
}
return "", msg
},
IsPasswordErr: func(err error) bool {
return strings.Contains(err.Error(), "Login failed for")
},
NewMetadataReader: NewReader,
NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer {
return metadata.NewDefaultWriter(NewReader(db, opts...))(db, w)
},
Copy: drivers.CopyWithInsert(placeholder),
})
}
func placeholder(n int) string {
return fmt.Sprintf("@p%d", n)
}
type NullUniqueIdentifier struct {
ID mssql.UniqueIdentifier
Valid bool
}
func (nui *NullUniqueIdentifier) Scan(v interface{}) error {
nui.Valid = false
if v == nil {
return nil
}
if err := nui.ID.Scan(v); err != nil {
return err
}
nui.Valid = true
return nil
}
func (nui NullUniqueIdentifier) String() string {
if nui.Valid {
return nui.ID.String()
}
return ""
}
================================================
FILE: drivers/sqlserver/sqlserver_test.go
================================================
package sqlserver_test
import (
"database/sql"
"flag"
"fmt"
"log"
"net/url"
"os"
"strings"
"testing"
dt "github.com/ory/dockertest/v3"
dc "github.com/ory/dockertest/v3/docker"
"github.com/xo/usql/drivers/metadata"
"github.com/xo/usql/drivers/sqlserver"
)
type Database struct {
BuildArgs []dc.BuildArg
RunOptions *dt.RunOptions
Exec []string
Driver string
URL string
ReadinessURL string
DockerPort string
Resource *dt.Resource
DB *sql.DB
Opts []metadata.ReaderOption
Reader metadata.BasicReader
}
var dbName string = "sakila"
const pw = "yourStrong123_Password"
var db = Database{
BuildArgs: []dc.BuildArg{
{Name: "BASE_IMAGE", Value: "mcr.microsoft.com/mssql/server:2019-latest"},
{Name: "SCHEMA_URL", Value: "https://raw.githubusercontent.com/jOOQ/sakila/main/sql-server-sakila-db/sql-server-sakila-schema.sql"},
{Name: "TARGET", Value: "/schema"},
{Name: "USER", Value: "mssql:0"},
},
RunOptions: &dt.RunOptions{
Name: "usql-sqlserver",
Env: []string{"ACCEPT_EULA=Y", "SA_PASSWORD=" + pw},
},
Exec: []string{"/opt/mssql-tools/bin/sqlcmd", "-S", "localhost", "-U", "sa", "-P", pw, "-d", "master", "-i", "/schema/sql-server-sakila-schema.sql"},
Driver: "sqlserver",
URL: "sqlserver://sa:" + url.QueryEscape(pw) + "@127.0.0.1:%s?database=" + dbName,
ReadinessURL: "sqlserver://sa:" + url.QueryEscape(pw) + "@127.0.0.1:%s",
DockerPort: "1433/tcp",
}
func TestMain(m *testing.M) {
cleanup := true
flag.BoolVar(&cleanup, "cleanup", true, "delete containers when finished")
flag.Parse()
pool, err := dt.NewPool("")
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
var ok bool
db.Resource, ok = pool.ContainerByName(db.RunOptions.Name)
if !ok {
buildOpts := &dt.BuildOptions{
ContextDir: "../testdata/docker",
BuildArgs: db.BuildArgs,
}
db.Resource, err = pool.BuildAndRunWithBuildOptions(buildOpts, db.RunOptions)
if err != nil {
log.Fatal("Could not start resource: ", err)
}
}
url := db.URL
if db.ReadinessURL != "" {
url = db.ReadinessURL
}
port := db.Resource.GetPort(db.DockerPort)
if db.DB, err = waitForDbConnection(db.Driver, pool, url, port); err != nil {
log.Fatal("Timed out waiting for db: ", err)
}
if len(db.Exec) != 0 {
exitCode, err := db.Resource.Exec(db.Exec, dt.ExecOptions{
StdIn: os.Stdin,
StdOut: os.Stdout,
StdErr: os.Stderr,
TTY: true,
})
if err != nil || exitCode != 0 {
log.Fatal("Could not load schema: ", err)
}
}
// Reconnect with actual URL if a separate URL for readiness checking was used
if db.ReadinessURL != "" {
if db.DB, err = waitForDbConnection(db.Driver, pool, db.URL, port); err != nil {
log.Fatal("Timed out waiting for db: ", err)
}
}
code := m.Run()
// You can't defer this because os.Exit doesn't care for defer
if cleanup {
if err := pool.Purge(db.Resource); err != nil {
log.Fatal("Could not purge resource: ", err)
}
}
os.Exit(code)
}
func waitForDbConnection(driver string, pool *dt.Pool, url string, port string) (*sql.DB, error) {
// exponential backoff-retry, because the application in the container might not be ready to accept connections yet
var db *sql.DB
if err := pool.Retry(func() error {
var err error
db, err = sql.Open(driver, fmt.Sprintf(url, port))
if err != nil {
return err
}
return db.Ping()
}); err != nil {
return nil, err
}
return db, nil
}
func TestColumns(t *testing.T) {
// Only testing sqlserver specific datatype formatting.
// The rest of the functionality is covered by informationschema/metadata_test.go:TestColumns
type test struct {
typeDef string
want string
}
schema := "dbo"
table := "test_dtypes"
tests := []test{
{typeDef: "bigint", want: "bigint"},
{typeDef: "numeric", want: "numeric"},
{typeDef: "numeric(4,2)", want: "numeric(4,2)"},
{typeDef: "numeric(18,0)", want: "numeric"},
{typeDef: "decimal", want: "decimal"},
{typeDef: "decimal(4,2)", want: "decimal(4,2)"},
{typeDef: "decimal(18,0)", want: "decimal"},
{typeDef: "bit", want: "bit"},
{typeDef: "smallint", want: "smallint"},
{typeDef: "smallmoney", want: "smallmoney"},
{typeDef: "int", want: "int"},
{typeDef: "tinyint", want: "tinyint"},
{typeDef: "money", want: "money"},
{typeDef: "float", want: "float"},
{typeDef: "float(11)", want: "real"},
{typeDef: "float(30)", want: "float"},
{typeDef: "real", want: "real"},
{typeDef: "date", want: "date"},
{typeDef: "datetimeoffset", want: "datetimeoffset"},
{typeDef: "datetimeoffset(5)", want: "datetimeoffset(5)"},
{typeDef: "datetimeoffset(7)", want: "datetimeoffset"},
{typeDef: "datetime2", want: "datetime2"},
{typeDef: "datetime2(5)", want: "datetime2(5)"},
{typeDef: "datetime2(7)", want: "datetime2"},
{typeDef: "smalldatetime", want: "smalldatetime"},
{typeDef: "datetime", want: "datetime"},
{typeDef: "time", want: "time"},
{typeDef: "time(5)", want: "time(5)"},
{typeDef: "time(7)", want: "time"},
{typeDef: "char", want: "char"},
{typeDef: "char(3)", want: "char(3)"},
{typeDef: "char(1)", want: "char"},
{typeDef: "varchar", want: "varchar"},
{typeDef: "varchar(12)", want: "varchar(12)"},
{typeDef: "varchar(1)", want: "varchar"},
{typeDef: "varchar(max)", want: "varchar(max)"},
{typeDef: "text", want: "text"},
{typeDef: "nchar", want: "nchar"},
{typeDef: "nchar(2)", want: "nchar(2)"},
{typeDef: "nchar(1)", want: "nchar"},
{typeDef: "nvarchar", want: "nvarchar"},
{typeDef: "nvarchar(12)", want: "nvarchar(12)"},
{typeDef: "nvarchar(1)", want: "nvarchar"},
{typeDef: "nvarchar(max)", want: "nvarchar(max)"},
{typeDef: "ntext", want: "ntext"},
{typeDef: "binary", want: "binary"},
{typeDef: "binary(12)", want: "binary(12)"},
{typeDef: "binary(1)", want: "binary"},
{typeDef: "varbinary", want: "varbinary"},
{typeDef: "varbinary(12)", want: "varbinary(12)"},
{typeDef: "varbinary(1)", want: "varbinary"},
{typeDef: "varbinary(max)", want: "varbinary(max)"},
{typeDef: "image", want: "image"},
{typeDef: "rowversion", want: "timestamp"},
{typeDef: "hierarchyid", want: "hierarchyid"},
{typeDef: "uniqueidentifier", want: "uniqueidentifier"},
{typeDef: "sql_variant", want: "sql_variant"},
{typeDef: "xml", want: "xml"},
{typeDef: "geometry", want: "geometry"},
{typeDef: "geography", want: "geography"},
}
// Create table
colExpressions := []string{}
for i, test := range tests {
colExpressions = append(colExpressions, fmt.Sprintf("column_%d %s", i, test.typeDef))
}
query := fmt.Sprintf("CREATE TABLE %s.%s (%s)", schema, table, strings.Join(colExpressions, ", "))
db.DB.Exec(query)
defer db.DB.Exec(fmt.Sprintf("DROP TABLE %s.%s", schema, table))
// Read data types
r := sqlserver.NewReader(db.DB).(metadata.ColumnReader)
result, err := r.Columns(metadata.Filter{Schema: schema, Parent: table})
if err != nil {
log.Fatalf("Could not read %s columns: %v", dbName, err)
}
actualTypes := []string{}
for result.Next() {
actualTypes = append(actualTypes, result.Get().DataType)
}
// Compare
for i, test := range tests {
if actualTypes[i] != test.want {
t.Errorf("Wrong %s column data type, expected:\n %s, got:\n %s", dbName, test.want, actualTypes[i])
}
}
}
================================================
FILE: drivers/testdata/.gitignore
================================================
*.actual.txt
================================================
FILE: drivers/testdata/csvq/.gitignore
================================================
*_copy
================================================
FILE: drivers/testdata/docker/Dockerfile
================================================
ARG BASE_IMAGE
FROM $BASE_IMAGE
ARG SCHEMA_URL
ARG TARGET
ARG USER
ADD --chown=$USER $SCHEMA_URL $TARGET/
RUN [ ! -d "$TARGET" ] || chmod -R 777 $TARGET/ || echo "failed to change perms of $TARGET, leaving as $(ls -la $TARGET/)"
================================================
FILE: drivers/testdata/gen-golden.sh
================================================
#!/usr/bin/env bash
pgsql_in_docker=false
pgsql_container=usql-pgsql
if [ "$pgsql_in_docker" != true ]; then
PGHOST="${PGHOST:-127.0.0.1}"
port=$(docker port "$pgsql_container" 5432/tcp)
PGPORT=${port##*:}
else
PGHOST="${PGHOST:-$pgsql_container}"
PGPORT=5432
fi
PGUSER="${PGUSER:-postgres}"
PGPASSWORD="${PGPASSWORD:-pw}"
export PGHOST PGPORT PGUSER PGPASSWORD
declare -A queries
queries=(
[descTable]="\d+ film*"
[listTables]="\dtvmsE+ film*"
[listFuncs]="\df+"
[listIndexes]="\di+"
[listSchemas]="\dn+"
[listDbs]="\l+"
)
for q in "${!queries[@]}"; do
query="${queries[$q]}"
cmd=(psql --no-psqlrc --command "$query")
if [ "$pgsql_in_docker" == true ]; then
docker run -it --rm -e PGHOST -e PGPORT -e PGUSER -e PGPASSWORD --link "$pgsql_container" postgres:13 "${cmd[@]}" >"pgsql.$q.golden.txt"
else
"${cmd[@]}" -o "pgsql.$q.golden.txt"
fi
done
mysql_in_docker=true
mysql_container=usql-mysql
if [ "$mysql_in_docker" != true ]; then
MYHOST="${MYHOST:-127.0.0.1}"
port=$(docker port "$mysql_container" 3306/tcp)
MYPORT=${port##*:}
else
MYHOST="${MYHOST:-$mysql_container}"
MYPORT=3306
fi
MYUSER="${MYUSER:-root}"
MYPASSWORD="${MYPASSWORD:-pw}"
declare -A queries
queries=(
[descTable]="DESC film; SHOW INDEX FROM film; DESC film_actor; SHOW INDEX FROM film_actor; DESC film_category; SHOW INDEX FROM film_category; DESC film_list; SHOW INDEX FROM film_list; DESC film_text; SHOW INDEX FROM film_text;"
[listTables]="SHOW TABLES LIKE 'film%'"
[listSchemas]="SHOW DATABASES"
)
for q in "${!queries[@]}"; do
query="${queries[$q]}"
cmd=(mysql -h "$MYHOST" -P "$MYPORT" -u "$MYUSER" --password="$MYPASSWORD" --no-auto-rehash --database sakila --execute "$query")
if [ "$mysql_in_docker" == true ]; then
docker run -it --rm --link "$mysql_container" mysql:8 "${cmd[@]}" 2>/dev/null >"mysql.$q.golden.txt"
else
"${cmd[@]}" 2>/dev/null >"mysql.$q.golden.txt"
fi
done
================================================
FILE: drivers/testdata/mysql.descTable.expected.txt
================================================
BASE TABLE "sakila.film"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
----------------------+---------------------------------------------------------------------+----------+-------------------+-------+----------------+-------+--------------
film_id | int unsigned | "NO" | | 10 | 0 | 10 | 0
title | varchar(255) | "NO" | | 255 | 0 | 10 | 765
description | text | "YES" | | 65535 | 0 | 10 | 65535
release_year | year | "YES" | | 0 | 0 | 10 | 0
language_id | int unsigned | "NO" | | 10 | 0 | 10 | 0
original_language_id | int unsigned | "YES" | | 10 | 0 | 10 | 0
rental_duration | tinyint unsigned | "NO" | 3 | 3 | 0 | 10 | 0
rental_rate | decimal(4,2) | "NO" | 4.99 | 4 | 2 | 10 | 0
length | smallint unsigned | "YES" | | 5 | 0 | 10 | 0
replacement_cost | decimal(5,2) | "NO" | 19.99 | 5 | 2 | 10 | 0
rating | enum('G','PG','PG-13','R','NC-17') | "YES" | G | 5 | 0 | 10 | 15
special_features | set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') | "YES" | | 54 | 0 | 10 | 162
last_update | timestamp | "NO" | CURRENT_TIMESTAMP | 0 | 0 | 10 | 0
Indexes:
"idx_fk_language_id" BTREE (language_id)
"idx_fk_original_language_id" BTREE (original_language_id)
"idx_title" BTREE (title)
"PRIMARY" PRIMARY_KEY, UNIQUE, BTREE (film_id)
Foreign-key constraints:
"fk_film_language" FOREIGN KEY (language_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
"fk_film_language_original" FOREIGN KEY (original_language_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
Referenced by:
TABLE "film" CONSTRAINT "fk_film_language" FOREIGN KEY (language_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
TABLE "film" CONSTRAINT "fk_film_language_original" FOREIGN KEY (original_language_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
BASE TABLE "sakila.film_actor"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
-------------+--------------+----------+-------------------+------+----------------+-------+--------------
actor_id | int unsigned | "NO" | | 10 | 0 | 10 | 0
film_id | int unsigned | "NO" | | 10 | 0 | 10 | 0
last_update | timestamp | "NO" | CURRENT_TIMESTAMP | 0 | 0 | 10 | 0
Indexes:
"idx_fk_film_id" BTREE (film_id)
"PRIMARY" PRIMARY_KEY, UNIQUE, BTREE (actor_id, film_id)
Foreign-key constraints:
"fk_film_actor_actor" FOREIGN KEY (actor_id) REFERENCES film_actor(actor_id) ON UPDATE CASCADE ON DELETE RESTRICT
"fk_film_actor_film" FOREIGN KEY (film_id) REFERENCES film_actor(actor_id) ON UPDATE CASCADE ON DELETE RESTRICT
Referenced by:
TABLE "film_actor" CONSTRAINT "fk_film_actor_actor" FOREIGN KEY (actor_id) REFERENCES film_actor(actor_id) ON UPDATE CASCADE ON DELETE RESTRICT
TABLE "film_actor" CONSTRAINT "fk_film_actor_film" FOREIGN KEY (film_id) REFERENCES film_actor(actor_id) ON UPDATE CASCADE ON DELETE RESTRICT
BASE TABLE "sakila.film_category"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
-------------+--------------+----------+-------------------+------+----------------+-------+--------------
film_id | int unsigned | "NO" | | 10 | 0 | 10 | 0
category_id | int unsigned | "NO" | | 10 | 0 | 10 | 0
last_update | timestamp | "NO" | CURRENT_TIMESTAMP | 0 | 0 | 10 | 0
Indexes:
"fk_film_category_category" BTREE (category_id)
"PRIMARY" PRIMARY_KEY, UNIQUE, BTREE (film_id, category_id)
Foreign-key constraints:
"fk_film_category_category" FOREIGN KEY (category_id) REFERENCES film_category(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
"fk_film_category_film" FOREIGN KEY (film_id) REFERENCES film_category(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
Referenced by:
TABLE "film_category" CONSTRAINT "fk_film_category_category" FOREIGN KEY (category_id) REFERENCES film_category(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
TABLE "film_category" CONSTRAINT "fk_film_category_film" FOREIGN KEY (film_id) REFERENCES film_category(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
BASE TABLE "sakila.film_text"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
-------------+--------------+----------+---------+-------+----------------+-------+--------------
film_id | int | "NO" | | 10 | 0 | 10 | 0
title | varchar(255) | "NO" | | 255 | 0 | 10 | 765
description | text | "YES" | | 65535 | 0 | 10 | 65535
Indexes:
"idx_title_description" FULLTEXT (title, description)
"PRIMARY" PRIMARY_KEY, UNIQUE, BTREE (film_id)
VIEW "sakila.film_list"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
-------------+------------------------------------+----------+---------+-------+----------------+-------+--------------
FID | int unsigned | "YES" | 0 | 10 | 0 | 10 | 0
title | varchar(255) | "YES" | | 255 | 0 | 10 | 765
description | text | "YES" | | 65535 | 0 | 10 | 65535
category | varchar(25) | "NO" | | 25 | 0 | 10 | 75
price | decimal(4,2) | "YES" | 4.99 | 4 | 2 | 10 | 0
length | smallint unsigned | "YES" | | 5 | 0 | 10 | 0
rating | enum('G','PG','PG-13','R','NC-17') | "YES" | G | 5 | 0 | 10 | 15
actors | text | "YES" | | 65535 | 0 | 10 | 65535
================================================
FILE: drivers/testdata/mysql.descTable.golden.txt
================================================
mysql: [Warning] Using a password on the command line interface can be insecure.
+----------------------+---------------------------------------------------------------------+------+-----+-------------------+-----------------------------------------------+
| Field | Type | Null | Key | Default | Extra |
+----------------------+---------------------------------------------------------------------+------+-----+-------------------+-----------------------------------------------+
| film_id | smallint unsigned | NO | PRI | NULL | auto_increment |
| title | varchar(255) | NO | MUL | NULL | |
| description | text | YES | | NULL | |
| release_year | year | YES | | NULL | |
| language_id | tinyint unsigned | NO | MUL | NULL | |
| original_language_id | tinyint unsigned | YES | MUL | NULL | |
| rental_duration | tinyint unsigned | NO | | 3 | |
| rental_rate | decimal(4,2) | NO | | 4.99 | |
| length | smallint unsigned | YES | | NULL | |
| replacement_cost | decimal(5,2) | NO | | 19.99 | |
| rating | enum('G','PG','PG-13','R','NC-17') | YES | | G | |
| special_features | set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') | YES | | NULL | |
| last_update | timestamp | NO | | CURRENT_TIMESTAMP | DEFAULT_GENERATED on update CURRENT_TIMESTAMP |
+----------------------+---------------------------------------------------------------------+------+-----+-------------------+-----------------------------------------------+
+-------+------------+-----------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-------+------------+-----------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| film | 0 | PRIMARY | 1 | film_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| film | 1 | idx_title | 1 | title | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| film | 1 | idx_fk_language_id | 1 | language_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| film | 1 | idx_fk_original_language_id | 1 | original_language_id | A | 0 | NULL | NULL | YES | BTREE | | | YES | NULL |
+-------+------------+-----------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
+-------------+-------------------+------+-----+-------------------+-----------------------------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-------------------+------+-----+-------------------+-----------------------------------------------+
| actor_id | smallint unsigned | NO | PRI | NULL | |
| film_id | smallint unsigned | NO | PRI | NULL | |
| last_update | timestamp | NO | | CURRENT_TIMESTAMP | DEFAULT_GENERATED on update CURRENT_TIMESTAMP |
+-------------+-------------------+------+-----+-------------------+-----------------------------------------------+
+------------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+------------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| film_actor | 0 | PRIMARY | 1 | actor_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| film_actor | 0 | PRIMARY | 2 | film_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| film_actor | 1 | idx_fk_film_id | 1 | film_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
+------------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
+-------------+-------------------+------+-----+-------------------+-----------------------------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-------------------+------+-----+-------------------+-----------------------------------------------+
| film_id | smallint unsigned | NO | PRI | NULL | |
| category_id | tinyint unsigned | NO | PRI | NULL | |
| last_update | timestamp | NO | | CURRENT_TIMESTAMP | DEFAULT_GENERATED on update CURRENT_TIMESTAMP |
+-------------+-------------------+------+-----+-------------------+-----------------------------------------------+
+---------------+------------+---------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+---------------+------------+---------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| film_category | 0 | PRIMARY | 1 | film_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| film_category | 0 | PRIMARY | 2 | category_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| film_category | 1 | fk_film_category_category | 1 | category_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
+---------------+------------+---------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
+-------------+------------------------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------------------------+------+-----+---------+-------+
| FID | smallint unsigned | YES | | 0 | |
| title | varchar(255) | YES | | NULL | |
| description | text | YES | | NULL | |
| category | varchar(25) | NO | | NULL | |
| price | decimal(4,2) | YES | | 4.99 | |
| length | smallint unsigned | YES | | NULL | |
| rating | enum('G','PG','PG-13','R','NC-17') | YES | | G | |
| actors | text | YES | | NULL | |
+-------------+------------------------------------+------+-----+---------+-------+
+-------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+-------+
| film_id | smallint | NO | PRI | NULL | |
| title | varchar(255) | NO | MUL | NULL | |
| description | text | YES | | NULL | |
+-------------+--------------+------+-----+---------+-------+
+-----------+------------+-----------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-----------+------------+-----------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| film_text | 0 | PRIMARY | 1 | film_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| film_text | 1 | idx_title_description | 1 | title | NULL | NULL | NULL | NULL | | FULLTEXT | | | YES | NULL |
| film_text | 1 | idx_title_description | 2 | description | NULL | NULL | NULL | NULL | YES | FULLTEXT | | | YES | NULL |
+-----------+------------+-----------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
================================================
FILE: drivers/testdata/mysql.listFuncs.expected.txt
================================================
List of functions
Schema | Name | Result data type | Argument data types | Type
--------+----------------------------+------------------+---------------------------------------------------------------------------------------------+-----------
sakila | film_in_stock | | p_film_id int, p_store_id int, OUT p_film_count int | PROCEDURE
sakila | film_not_in_stock | | p_film_id int, p_store_id int, OUT p_film_count int | PROCEDURE
sakila | get_customer_balance | decimal | p_customer_id int, p_effective_date datetime | FUNCTION
sakila | inventory_held_by_customer | int | p_inventory_id int | FUNCTION
sakila | inventory_in_stock | tinyint | p_inventory_id int | FUNCTION
sakila | rewards_report | | min_monthly_purchases tinyint, min_dollar_amount_purchased decimal, OUT count_rewardees int | PROCEDURE
(6 rows)
================================================
FILE: drivers/testdata/mysql.listIndexes.expected.txt
================================================
List of indexes
Schema | Name | Type | Table | Primary? | Unique?
--------+-----------------------------+----------+---------------+----------+---------
sakila | idx_actor_last_name | BTREE | actor | "NO" | "NO"
sakila | PRIMARY | BTREE | actor | "YES" | "YES"
sakila | idx_fk_city_id | BTREE | address | "NO" | "NO"
sakila | PRIMARY | BTREE | address | "YES" | "YES"
sakila | PRIMARY | BTREE | category | "YES" | "YES"
sakila | idx_fk_country_id | BTREE | city | "NO" | "NO"
sakila | PRIMARY | BTREE | city | "YES" | "YES"
sakila | PRIMARY | BTREE | country | "YES" | "YES"
sakila | idx_fk_address_id | BTREE | customer | "NO" | "NO"
sakila | idx_fk_store_id | BTREE | customer | "NO" | "NO"
sakila | idx_last_name | BTREE | customer | "NO" | "NO"
sakila | PRIMARY | BTREE | customer | "YES" | "YES"
sakila | idx_fk_language_id | BTREE | film | "NO" | "NO"
sakila | idx_fk_original_language_id | BTREE | film | "NO" | "NO"
sakila | idx_title | BTREE | film | "NO" | "NO"
sakila | PRIMARY | BTREE | film | "YES" | "YES"
sakila | idx_fk_film_id | BTREE | film_actor | "NO" | "NO"
sakila | PRIMARY | BTREE | film_actor | "YES" | "YES"
sakila | fk_film_category_category | BTREE | film_category | "NO" | "NO"
sakila | PRIMARY | BTREE | film_category | "YES" | "YES"
sakila | idx_title_description | FULLTEXT | film_text | "NO" | "NO"
sakila | PRIMARY | BTREE | film_text | "YES" | "YES"
sakila | idx_fk_film_id | BTREE | inventory | "NO" | "NO"
sakila | idx_store_id_film_id | BTREE | inventory | "NO" | "NO"
sakila | PRIMARY | BTREE | inventory | "YES" | "YES"
sakila | PRIMARY | BTREE | language | "YES" | "YES"
sakila | fk_payment_rental | BTREE | payment | "NO" | "NO"
sakila | idx_fk_customer_id | BTREE | payment | "NO" | "NO"
sakila | idx_fk_staff_id | BTREE | payment | "NO" | "NO"
sakila | PRIMARY | BTREE | payment | "YES" | "YES"
sakila | idx_fk_customer_id | BTREE | rental | "NO" | "NO"
sakila | idx_fk_inventory_id | BTREE | rental | "NO" | "NO"
sakila | idx_fk_staff_id | BTREE | rental | "NO" | "NO"
sakila | PRIMARY | BTREE | rental | "YES" | "YES"
sakila | rental_date | BTREE | rental | "NO" | "YES"
sakila | idx_fk_address_id | BTREE | staff | "NO" | "NO"
sakila | idx_fk_store_id | BTREE | staff | "NO" | "NO"
sakila | PRIMARY | BTREE | staff | "YES" | "YES"
sakila | idx_fk_address_id | BTREE | store | "NO" | "NO"
sakila | idx_unique_manager | BTREE | store | "NO" | "YES"
sakila | PRIMARY | BTREE | store | "YES" | "YES"
(41 rows)
================================================
FILE: drivers/testdata/mysql.listSchemas.expected.txt
================================================
List of schemas
Schema | Catalog
--------+---------
sakila | def
(1 row)
================================================
FILE: drivers/testdata/mysql.listSchemas.golden.txt
================================================
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sakila |
| sys |
+--------------------+
================================================
FILE: drivers/testdata/mysql.listTables.expected.txt
================================================
List of relations
Schema | Name | Type | Rows | Size | Comment
--------+---------------+------------+------+------+---------
sakila | film | BASE TABLE | 0 | |
sakila | film_actor | BASE TABLE | 0 | |
sakila | film_category | BASE TABLE | 0 | |
sakila | film_text | BASE TABLE | 0 | |
sakila | film_list | VIEW | 0 | |
(5 rows)
================================================
FILE: drivers/testdata/mysql.listTables.golden.txt
================================================
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------------------------+
| Tables_in_sakila (film%) |
+--------------------------+
| film |
| film_actor |
| film_category |
| film_list |
| film_text |
+--------------------------+
================================================
FILE: drivers/testdata/pgsql.descTable.expected.txt
================================================
table "public.film"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
----------------------+--------------------------------+----------+---------------------------------------+------+----------------+-------+--------------
film_id | integer | "NO" | nextval('film_film_id_seq'::regclass) | 32 | 0 | 2 | 0
title | character varying(255) | "NO" | | 255 | 0 | 10 | 1020
description | text | "YES" | | 0 | 0 | 10 | 1073741824
release_year | integer | "YES" | | 32 | 0 | 2 | 0
language_id | smallint | "NO" | | 16 | 0 | 2 | 0
original_language_id | smallint | "YES" | | 16 | 0 | 2 | 0
rental_duration | smallint | "NO" | 3 | 16 | 0 | 2 | 0
rental_rate | numeric(4,2) | "NO" | 4.99 | 4 | 2 | 10 | 0
length | smallint | "YES" | | 16 | 0 | 2 | 0
replacement_cost | numeric(5,2) | "NO" | 19.99 | 5 | 2 | 10 | 0
rating | USER-DEFINED | "YES" | 'G'::mpaa_rating | 0 | 0 | 10 | 0
last_update | timestamp(6) without time zone | "NO" | now() | 6 | 0 | 10 | 0
special_features | ARRAY | "YES" | | 0 | 0 | 10 | 0
fulltext | tsvector | "NO" | | 0 | 0 | 10 | 0
Indexes:
"film_fulltext_idx" gist (fulltext)
"film_pkey" PRIMARY_KEY, UNIQUE, btree (film_id)
"idx_fk_language_id" btree (language_id)
"idx_fk_original_language_id" btree (original_language_id)
"idx_title" btree (title)
Foreign-key constraints:
"film_language_id_fkey" FOREIGN KEY (language_id) REFERENCES language(language_id) ON UPDATE CASCADE ON DELETE RESTRICT
"film_original_language_id_fkey" FOREIGN KEY (original_language_id) REFERENCES language(language_id) ON UPDATE CASCADE ON DELETE RESTRICT
Referenced by:
TABLE "film_actor" CONSTRAINT "film_actor_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
TABLE "film_category" CONSTRAINT "film_category_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
TABLE "inventory" CONSTRAINT "inventory_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
Triggers:
"film_fulltext_trigger" CREATE TRIGGER film_fulltext_trigger BEFORE INSERT OR UPDATE ON film FOR EACH ROW EXECUTE FUNCTION tsvector_update_trigger('fulltext', 'pg_catalog.english', 'title', 'description')
"last_updated" CREATE TRIGGER last_updated BEFORE UPDATE ON film FOR EACH ROW EXECUTE FUNCTION last_updated()
table "public.film_actor"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
-------------+--------------------------------+----------+---------+------+----------------+-------+--------------
actor_id | smallint | "NO" | | 16 | 0 | 2 | 0
film_id | smallint | "NO" | | 16 | 0 | 2 | 0
last_update | timestamp(6) without time zone | "NO" | now() | 6 | 0 | 10 | 0
Indexes:
"film_actor_pkey" PRIMARY_KEY, UNIQUE, btree (actor_id, film_id)
"idx_fk_film_id" btree (film_id)
Foreign-key constraints:
"film_actor_actor_id_fkey" FOREIGN KEY (actor_id) REFERENCES actor(actor_id) ON UPDATE CASCADE ON DELETE RESTRICT
"film_actor_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
Triggers:
"last_updated" CREATE TRIGGER last_updated BEFORE UPDATE ON film_actor FOR EACH ROW EXECUTE FUNCTION last_updated()
table "public.film_category"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
-------------+--------------------------------+----------+---------+------+----------------+-------+--------------
film_id | smallint | "NO" | | 16 | 0 | 2 | 0
category_id | smallint | "NO" | | 16 | 0 | 2 | 0
last_update | timestamp(6) without time zone | "NO" | now() | 6 | 0 | 10 | 0
Indexes:
"film_category_pkey" PRIMARY_KEY, UNIQUE, btree (film_id, category_id)
Foreign-key constraints:
"film_category_category_id_fkey" FOREIGN KEY (category_id) REFERENCES category(category_id) ON UPDATE CASCADE ON DELETE RESTRICT
"film_category_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
Triggers:
"last_updated" CREATE TRIGGER last_updated BEFORE UPDATE ON film_category FOR EACH ROW EXECUTE FUNCTION last_updated()
view "public.film_list"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
-------------+------------------------+----------+---------+------+----------------+-------+--------------
fid | integer | "YES" | | 32 | 0 | 2 | 0
title | character varying(255) | "YES" | | 255 | 0 | 10 | 1020
description | text | "YES" | | 0 | 0 | 10 | 1073741824
category | character varying(25) | "YES" | | 25 | 0 | 10 | 100
price | numeric(4,2) | "YES" | | 4 | 2 | 10 | 0
length | smallint | "YES" | | 16 | 0 | 2 | 0
rating | USER-DEFINED | "YES" | | 0 | 0 | 10 | 0
actors | text | "YES" | | 0 | 0 | 10 | 1073741824
Sequence "public.film_film_id_seq"
Type | Start | Min | Max | Increment | Cycles?
--------+-------+-----+---------------------+-----------+---------
bigint | 1 | 1 | 9223372036854775807 | 1 | "NO"
Index "public.film_actor_pkey"
Name | Type
----------+----------
actor_id | smallint
film_id | smallint
primary key, btree, for table film_actor
Index "public.film_category_pkey"
Name | Type
-------------+----------
film_id | smallint
category_id | smallint
primary key, btree, for table film_category
Index "public.film_fulltext_idx"
Name | Type
----------+-----------
fulltext | gtsvector
gist, for table film
Index "public.film_pkey"
Name | Type
---------+---------
film_id | integer
primary key, btree, for table film
================================================
FILE: drivers/testdata/pgsql.descTable.golden.txt
================================================
Table "public.film"
Column | Type | Collation | Nullable | Default
----------------------+-----------------------------+-----------+----------+---------------------------------------
film_id | integer | | not null | nextval('film_film_id_seq'::regclass)
title | character varying(255) | | not null |
description | text | | |
release_year | year | | |
language_id | smallint | | not null |
original_language_id | smallint | | |
rental_duration | smallint | | not null | 3
rental_rate | numeric(4,2) | | not null | 4.99
length | smallint | | |
replacement_cost | numeric(5,2) | | not null | 19.99
rating | mpaa_rating | | | 'G'::mpaa_rating
last_update | timestamp without time zone | | not null | now()
special_features | text[] | | |
fulltext | tsvector | | not null |
Indexes:
"film_pkey" PRIMARY KEY, btree (film_id)
"film_fulltext_idx" gist (fulltext)
"idx_fk_language_id" btree (language_id)
"idx_fk_original_language_id" btree (original_language_id)
"idx_title" btree (title)
Foreign-key constraints:
"film_language_id_fkey" FOREIGN KEY (language_id) REFERENCES language(language_id) ON UPDATE CASCADE ON DELETE RESTRICT
"film_original_language_id_fkey" FOREIGN KEY (original_language_id) REFERENCES language(language_id) ON UPDATE CASCADE ON DELETE RESTRICT
Referenced by:
TABLE "film_actor" CONSTRAINT "film_actor_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
TABLE "film_category" CONSTRAINT "film_category_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
TABLE "inventory" CONSTRAINT "inventory_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
Triggers:
film_fulltext_trigger BEFORE INSERT OR UPDATE ON film FOR EACH ROW EXECUTE FUNCTION tsvector_update_trigger('fulltext', 'pg_catalog.english', 'title', 'description')
last_updated BEFORE UPDATE ON film FOR EACH ROW EXECUTE FUNCTION last_updated()
Table "public.film_actor"
Column | Type | Collation | Nullable | Default
-------------+-----------------------------+-----------+----------+---------
actor_id | smallint | | not null |
film_id | smallint | | not null |
last_update | timestamp without time zone | | not null | now()
Indexes:
"film_actor_pkey" PRIMARY KEY, btree (actor_id, film_id)
"idx_fk_film_id" btree (film_id)
Foreign-key constraints:
"film_actor_actor_id_fkey" FOREIGN KEY (actor_id) REFERENCES actor(actor_id) ON UPDATE CASCADE ON DELETE RESTRICT
"film_actor_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
Triggers:
last_updated BEFORE UPDATE ON film_actor FOR EACH ROW EXECUTE FUNCTION last_updated()
Index "public.film_actor_pkey"
Column | Type | Key? | Definition
----------+----------+------+------------
actor_id | smallint | yes | actor_id
film_id | smallint | yes | film_id
primary key, btree, for table "public.film_actor"
Table "public.film_category"
Column | Type | Collation | Nullable | Default
-------------+-----------------------------+-----------+----------+---------
film_id | smallint | | not null |
category_id | smallint | | not null |
last_update | timestamp without time zone | | not null | now()
Indexes:
"film_category_pkey" PRIMARY KEY, btree (film_id, category_id)
Foreign-key constraints:
"film_category_category_id_fkey" FOREIGN KEY (category_id) REFERENCES category(category_id) ON UPDATE CASCADE ON DELETE RESTRICT
"film_category_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
Triggers:
last_updated BEFORE UPDATE ON film_category FOR EACH ROW EXECUTE FUNCTION last_updated()
Index "public.film_category_pkey"
Column | Type | Key? | Definition
-------------+----------+------+-------------
film_id | smallint | yes | film_id
category_id | smallint | yes | category_id
primary key, btree, for table "public.film_category"
Sequence "public.film_film_id_seq"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
Index "public.film_fulltext_idx"
Column | Type | Key? | Definition
----------+-----------+------+------------
fulltext | gtsvector | yes | fulltext
gist, for table "public.film"
View "public.film_list"
Column | Type | Collation | Nullable | Default
-------------+------------------------+-----------+----------+---------
fid | integer | | |
title | character varying(255) | | |
description | text | | |
category | character varying(25) | | |
price | numeric(4,2) | | |
length | smallint | | |
rating | mpaa_rating | | |
actors | text | | |
Index "public.film_pkey"
Column | Type | Key? | Definition
---------+---------+------+------------
film_id | integer | yes | film_id
primary key, btree, for table "public.film"
================================================
FILE: drivers/testdata/pgsql.listDbs.golden.txt
================================================
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+------------+------------+-----------------------
postgres | postgres | UTF8 | en_US.utf8 | en_US.utf8 |
template0 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | =c/postgres +
| | | | | postgres=CTc/postgres
(3 rows)
================================================
FILE: drivers/testdata/pgsql.listFuncs.expected.txt
================================================
List of functions
Schema | Name | Result data type | Argument data types | Type
--------+----------------------------+------------------+---------------------------------------------------------------------+----------
public | _group_concat | text | text, text | FUNCTION
public | film_in_stock | integer | p_film_id integer, p_store_id integer, OUT p_film_count integer | FUNCTION
public | film_not_in_stock | integer | p_film_id integer, p_store_id integer, OUT p_film_count integer | FUNCTION
public | get_customer_balance | numeric | p_customer_id integer, p_effective_date timestamp without time zone | FUNCTION
public | group_concat | text | text |
public | inventory_held_by_customer | integer | p_inventory_id integer | FUNCTION
public | inventory_in_stock | boolean | p_inventory_id integer | FUNCTION
public | last_day | date | timestamp without time zone | FUNCTION
public | last_updated | trigger | | FUNCTION
public | rewards_report | USER-DEFINED | min_monthly_purchases integer, min_dollar_amount_purchased numeric | FUNCTION
(10 rows)
================================================
FILE: drivers/testdata/pgsql.listFuncs.golden.txt
================================================
List of functions
Schema | Name | Result data type | Argument data types | Type
--------+----------------------------+------------------+---------------------------------------------------------------------+------
public | _group_concat | text | text, text | func
public | film_in_stock | SETOF integer | p_film_id integer, p_store_id integer, OUT p_film_count integer | func
public | film_not_in_stock | SETOF integer | p_film_id integer, p_store_id integer, OUT p_film_count integer | func
public | get_customer_balance | numeric | p_customer_id integer, p_effective_date timestamp without time zone | func
public | group_concat | text | text | agg
public | inventory_held_by_customer | integer | p_inventory_id integer | func
public | inventory_in_stock | boolean | p_inventory_id integer | func
public | last_day | date | timestamp without time zone | func
public | last_updated | trigger | | func
public | rewards_report | SETOF customer | min_monthly_purchases integer, min_dollar_amount_purchased numeric | func
(10 rows)
================================================
FILE: drivers/testdata/pgsql.listIndexes.expected.txt
================================================
List of indexes
Schema | Name | Type | Table | Primary? | Unique?
--------+-----------------------------------------------------+-------+------------------+----------+---------
public | actor_pkey | index | actor | "YES" | "YES"
public | address_pkey | index | address | "YES" | "YES"
public | category_pkey | index | category | "YES" | "YES"
public | city_pkey | index | city | "YES" | "YES"
public | country_pkey | index | country | "YES" | "YES"
public | customer_pkey | index | customer | "YES" | "YES"
public | film_actor_pkey | index | film_actor | "YES" | "YES"
public | film_category_pkey | index | film_category | "YES" | "YES"
public | film_fulltext_idx | index | film | "NO" | "NO"
public | film_pkey | index | film | "YES" | "YES"
public | idx_actor_last_name | index | actor | "NO" | "NO"
public | idx_fk_address_id | index | customer | "NO" | "NO"
public | idx_fk_city_id | index | address | "NO" | "NO"
public | idx_fk_country_id | index | city | "NO" | "NO"
public | idx_fk_customer_id | index | payment | "NO" | "NO"
public | idx_fk_film_id | index | film_actor | "NO" | "NO"
public | idx_fk_inventory_id | index | rental | "NO" | "NO"
public | idx_fk_language_id | index | film | "NO" | "NO"
public | idx_fk_original_language_id | index | film | "NO" | "NO"
public | idx_fk_payment_p2007_01_customer_id | index | payment_p2007_01 | "NO" | "NO"
public | idx_fk_payment_p2007_01_staff_id | index | payment_p2007_01 | "NO" | "NO"
public | idx_fk_payment_p2007_02_customer_id | index | payment_p2007_02 | "NO" | "NO"
public | idx_fk_payment_p2007_02_staff_id | index | payment_p2007_02 | "NO" | "NO"
public | idx_fk_payment_p2007_03_customer_id | index | payment_p2007_03 | "NO" | "NO"
public | idx_fk_payment_p2007_03_staff_id | index | payment_p2007_03 | "NO" | "NO"
public | idx_fk_payment_p2007_04_customer_id | index | payment_p2007_04 | "NO" | "NO"
public | idx_fk_payment_p2007_04_staff_id | index | payment_p2007_04 | "NO" | "NO"
public | idx_fk_payment_p2007_05_customer_id | index | payment_p2007_05 | "NO" | "NO"
public | idx_fk_payment_p2007_05_staff_id | index | payment_p2007_05 | "NO" | "NO"
public | idx_fk_payment_p2007_06_customer_id | index | payment_p2007_06 | "NO" | "NO"
public | idx_fk_payment_p2007_06_staff_id | index | payment_p2007_06 | "NO" | "NO"
public | idx_fk_staff_id | index | payment | "NO" | "NO"
public | idx_fk_store_id | index | customer | "NO" | "NO"
public | idx_last_name | index | customer | "NO" | "NO"
public | idx_store_id_film_id | index | inventory | "NO" | "NO"
public | idx_title | index | film | "NO" | "NO"
public | idx_unq_manager_staff_id | index | store | "YES" | "NO"
public | idx_unq_rental_rental_date_inventory_id_customer_id | index | rental | "YES" | "NO"
public | inventory_pkey | index | inventory | "YES" | "YES"
public | language_pkey | index | language | "YES" | "YES"
public | payment_pkey | index | payment | "YES" | "YES"
public | rental_pkey | index | rental | "YES" | "YES"
public | staff_pkey | index | staff | "YES" | "YES"
public | store_pkey | index | store | "YES" | "YES"
(44 rows)
================================================
FILE: drivers/testdata/pgsql.listIndexes.golden.txt
================================================
List of relations
Schema | Name | Type | Owner | Table
--------+-----------------------------------------------------+-------+----------+------------------
public | actor_pkey | index | postgres | actor
public | address_pkey | index | postgres | address
public | category_pkey | index | postgres | category
public | city_pkey | index | postgres | city
public | country_pkey | index | postgres | country
public | customer_pkey | index | postgres | customer
public | film_actor_pkey | index | postgres | film_actor
public | film_category_pkey | index | postgres | film_category
public | film_fulltext_idx | index | postgres | film
public | film_pkey | index | postgres | film
public | idx_actor_last_name | index | postgres | actor
public | idx_fk_address_id | index | postgres | customer
public | idx_fk_city_id | index | postgres | address
public | idx_fk_country_id | index | postgres | city
public | idx_fk_customer_id | index | postgres | payment
public | idx_fk_film_id | index | postgres | film_actor
public | idx_fk_inventory_id | index | postgres | rental
public | idx_fk_language_id | index | postgres | film
public | idx_fk_original_language_id | index | postgres | film
public | idx_fk_payment_p2007_01_customer_id | index | postgres | payment_p2007_01
public | idx_fk_payment_p2007_01_staff_id | index | postgres | payment_p2007_01
public | idx_fk_payment_p2007_02_customer_id | index | postgres | payment_p2007_02
public | idx_fk_payment_p2007_02_staff_id | index | postgres | payment_p2007_02
public | idx_fk_payment_p2007_03_customer_id | index | postgres | payment_p2007_03
public | idx_fk_payment_p2007_03_staff_id | index | postgres | payment_p2007_03
public | idx_fk_payment_p2007_04_customer_id | index | postgres | payment_p2007_04
public | idx_fk_payment_p2007_04_staff_id | index | postgres | payment_p2007_04
public | idx_fk_payment_p2007_05_customer_id | index | postgres | payment_p2007_05
public | idx_fk_payment_p2007_05_staff_id | index | postgres | payment_p2007_05
public | idx_fk_payment_p2007_06_customer_id | index | postgres | payment_p2007_06
public | idx_fk_payment_p2007_06_staff_id | index | postgres | payment_p2007_06
public | idx_fk_staff_id | index | postgres | payment
public | idx_fk_store_id | index | postgres | customer
public | idx_last_name | index | postgres | customer
public | idx_store_id_film_id | index | postgres | inventory
public | idx_title | index | postgres | film
public | idx_unq_manager_staff_id | index | postgres | store
public | idx_unq_rental_rental_date_inventory_id_customer_id | index | postgres | rental
public | inventory_pkey | index | postgres | inventory
public | language_pkey | index | postgres | language
public | payment_pkey | index | postgres | payment
public | rental_pkey | index | postgres | rental
public | staff_pkey | index | postgres | staff
public | store_pkey | index | postgres | store
(44 rows)
================================================
FILE: drivers/testdata/pgsql.listSchemas.expected.txt
================================================
List of schemas
Schema | Catalog
--------+----------
public | postgres
(1 row)
================================================
FILE: drivers/testdata/pgsql.listSchemas.golden.txt
================================================
List of schemas
Name | Owner
--------+----------
public | postgres
(1 row)
================================================
FILE: drivers/testdata/pgsql.listTables.expected.txt
================================================
List of relations
Schema | Name | Type | Rows | Size | Comment
--------+------------------+----------+------+------------+---------
public | film_film_id_seq | sequence | 1 | 8192 bytes |
public | film | table | 0 | 8192 bytes |
public | film_actor | table | 0 | 0 bytes |
public | film_category | table | 0 | 0 bytes |
public | film_list | view | 0 | 0 bytes |
(5 rows)
================================================
FILE: drivers/testdata/pgsql.listTables.golden.txt
================================================
List of relations
Schema | Name | Type | Owner
--------+------------------+----------+----------
public | film | table | postgres
public | film_actor | table | postgres
public | film_category | table | postgres
public | film_film_id_seq | sequence | postgres
public | film_list | view | postgres
(5 rows)
================================================
FILE: drivers/testdata/sqlserver.descTable.expected.txt
================================================
BASE TABLE "dbo.film"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
----------------------+--------------+----------+-------------+------------+----------------+-------+--------------
film_id | int | "NO" | | 10 | 0 | 10 | 0
title | varchar(255) | "NO" | | 255 | 0 | 10 | 255
description | text | "YES" | (NULL) | 2147483647 | 0 | 10 | 2147483647
release_year | varchar(4) | "YES" | | 4 | 0 | 10 | 4
language_id | int | "NO" | | 10 | 0 | 10 | 0
original_language_id | int | "YES" | (NULL) | 10 | 0 | 10 | 0
rental_duration | tinyint | "NO" | ((3)) | 3 | 0 | 10 | 0
rental_rate | decimal(4,2) | "NO" | ((4.99)) | 4 | 2 | 10 | 0
length | smallint | "YES" | (NULL) | 5 | 0 | 10 | 0
replacement_cost | decimal(5,2) | "NO" | ((19.99)) | 5 | 2 | 10 | 0
rating | varchar(10) | "YES" | ('G') | 10 | 0 | 10 | 10
special_features | varchar(255) | "YES" | (NULL) | 255 | 0 | 10 | 255
last_update | datetime | "NO" | (getdate()) | 3 | 0 | 10 | 0
Indexes:
"" HEAP (language_id, original_language_id, film_id)
"idx_fk_language_id" NONCLUSTERED (language_id)
"idx_fk_original_language_id" NONCLUSTERED (original_language_id)
"PK__film__349764A85F0D1F82" PRIMARY_KEY, UNIQUE, NONCLUSTERED (film_id)
BASE TABLE "dbo.film_actor"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
-------------+----------+----------+-------------+------+----------------+-------+--------------
actor_id | int | "NO" | | 10 | 0 | 10 | 0
film_id | int | "NO" | | 10 | 0 | 10 | 0
last_update | datetime | "NO" | (getdate()) | 3 | 0 | 10 | 0
Indexes:
"" HEAP (actor_id, film_id, actor_id, film_id)
"idx_fk_film_actor_actor" NONCLUSTERED (actor_id)
"idx_fk_film_actor_film" NONCLUSTERED (film_id)
"PK__film_act__086D31FFE010698E" PRIMARY_KEY, UNIQUE, NONCLUSTERED (actor_id, film_id)
BASE TABLE "dbo.film_category"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
-------------+----------+----------+-------------+------+----------------+-------+--------------
film_id | int | "NO" | | 10 | 0 | 10 | 0
category_id | int | "NO" | | 10 | 0 | 10 | 0
last_update | datetime | "NO" | (getdate()) | 3 | 0 | 10 | 0
Indexes:
"" HEAP (category_id, film_id, film_id, category_id)
"idx_fk_film_category_category" NONCLUSTERED (category_id)
"idx_fk_film_category_film" NONCLUSTERED (film_id)
"PK__film_cat__69C38A33EABC8336" PRIMARY_KEY, UNIQUE, NONCLUSTERED (film_id, category_id)
BASE TABLE "dbo.film_text"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
-------------+--------------+----------+---------+------------+----------------+-------+--------------
film_id | int | "NO" | | 10 | 0 | 10 | 0
title | varchar(255) | "NO" | | 255 | 0 | 10 | 255
description | text | "YES" | | 2147483647 | 0 | 10 | 2147483647
Indexes:
"" HEAP (film_id)
"PK__film_tex__349764A85D245C83" PRIMARY_KEY, UNIQUE, NONCLUSTERED (film_id)
VIEW "dbo.film_list"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
-------------+--------------+----------+---------+------------+----------------+-------+--------------
FID | int | "YES" | | 10 | 0 | 10 | 0
title | varchar(255) | "YES" | | 255 | 0 | 10 | 255
description | text | "YES" | | 2147483647 | 0 | 10 | 2147483647
category | varchar(25) | "NO" | | 25 | 0 | 10 | 25
price | decimal(4,2) | "YES" | | 4 | 2 | 10 | 0
length | smallint | "YES" | | 5 | 0 | 10 | 0
rating | varchar(10) | "YES" | | 10 | 0 | 10 | 10
actors | varchar(91) | "NO" | | 91 | 0 | 10 | 91
================================================
FILE: drivers/testdata/sqlserver.listFuncs.expected.txt
================================================
(0 rows)
================================================
FILE: drivers/testdata/sqlserver.listIndexes.expected.txt
================================================
List of indexes
Schema | Name | Type | Table | Primary? | Unique?
--------+--------------------------------+--------------+---------------+----------+---------
dbo | | HEAP | actor | "NO" | "NO"
dbo | idx_actor_last_name | NONCLUSTERED | actor | "NO" | "NO"
dbo | PK__actor__8B2447B565179537 | NONCLUSTERED | actor | "YES" | "YES"
dbo | | HEAP | address | "NO" | "NO"
dbo | idx_fk_city_id | NONCLUSTERED | address | "NO" | "NO"
dbo | PK__address__CAA247C920A28CDC | NONCLUSTERED | address | "YES" | "YES"
dbo | | HEAP | category | "NO" | "NO"
dbo | PK__category__D54EE9B5C54BFE50 | NONCLUSTERED | category | "YES" | "YES"
dbo | | HEAP | city | "NO" | "NO"
dbo | idx_fk_country_id | NONCLUSTERED | city | "NO" | "NO"
dbo | PK__city__031491A980EA569B | NONCLUSTERED | city | "YES" | "YES"
dbo | | HEAP | country | "NO" | "NO"
dbo | PK__country__7E8CD054CAE966BB | NONCLUSTERED | country | "YES" | "YES"
dbo | | HEAP | customer | "NO" | "NO"
dbo | idx_fk_address_id | NONCLUSTERED | customer | "NO" | "NO"
dbo | idx_fk_store_id | NONCLUSTERED | customer | "NO" | "NO"
dbo | idx_last_name | NONCLUSTERED | customer | "NO" | "NO"
dbo | PK__customer__CD65CB84BB7D0A31 | NONCLUSTERED | customer | "YES" | "YES"
dbo | | HEAP | film | "NO" | "NO"
dbo | idx_fk_language_id | NONCLUSTERED | film | "NO" | "NO"
dbo | idx_fk_original_language_id | NONCLUSTERED | film | "NO" | "NO"
dbo | PK__film__349764A85F0D1F82 | NONCLUSTERED | film | "YES" | "YES"
dbo | | HEAP | film_actor | "NO" | "NO"
dbo | idx_fk_film_actor_actor | NONCLUSTERED | film_actor | "NO" | "NO"
dbo | idx_fk_film_actor_film | NONCLUSTERED | film_actor | "NO" | "NO"
dbo | PK__film_act__086D31FFE010698E | NONCLUSTERED | film_actor | "YES" | "YES"
dbo | | HEAP | film_category | "NO" | "NO"
dbo | idx_fk_film_category_category | NONCLUSTERED | film_category | "NO" | "NO"
dbo | idx_fk_film_category_film | NONCLUSTERED | film_category | "NO" | "NO"
dbo | PK__film_cat__69C38A33EABC8336 | NONCLUSTERED | film_category | "YES" | "YES"
dbo | | HEAP | film_text | "NO" | "NO"
dbo | PK__film_tex__349764A85D245C83 | NONCLUSTERED | film_text | "YES" | "YES"
dbo | | HEAP | inventory | "NO" | "NO"
dbo | idx_fk_film_id | NONCLUSTERED | inventory | "NO" | "NO"
dbo | idx_fk_film_id_store_id | NONCLUSTERED | inventory | "NO" | "NO"
dbo | PK__inventor__B59ACC48C0DED777 | NONCLUSTERED | inventory | "YES" | "YES"
dbo | | HEAP | language | "NO" | "NO"
dbo | PK__language__804CF6B2AD65E24B | NONCLUSTERED | language | "YES" | "YES"
dbo | | HEAP | payment | "NO" | "NO"
dbo | idx_fk_customer_id | NONCLUSTERED | payment | "NO" | "NO"
dbo | idx_fk_staff_id | NONCLUSTERED | payment | "NO" | "NO"
dbo | PK__payment__ED1FC9EBDD7F3474 | NONCLUSTERED | payment | "YES" | "YES"
dbo | | HEAP | rental | "NO" | "NO"
dbo | idx_fk_customer_id | NONCLUSTERED | rental | "NO" | "NO"
dbo | idx_fk_inventory_id | NONCLUSTERED | rental | "NO" | "NO"
dbo | idx_fk_staff_id | NONCLUSTERED | rental | "NO" | "NO"
dbo | idx_uq | NONCLUSTERED | rental | "YES" | "NO"
dbo | PK__rental__67DB611A79AF93E5 | NONCLUSTERED | rental | "YES" | "YES"
dbo | | HEAP | staff | "NO" | "NO"
dbo | idx_fk_address_id | NONCLUSTERED | staff | "NO" | "NO"
dbo | idx_fk_store_id | NONCLUSTERED | staff | "NO" | "NO"
dbo | PK__staff__1963DD9DFC0374BE | NONCLUSTERED | staff | "YES" | "YES"
dbo | | HEAP | staff_copy | "NO" | "NO"
dbo | | HEAP | store | "NO" | "NO"
dbo | idx_fk_address_id | NONCLUSTERED | store | "YES" | "NO"
dbo | idx_fk_store_address | NONCLUSTERED | store | "NO" | "NO"
dbo | PK__store__A2F2A30D66044831 | NONCLUSTERED | store | "YES" | "YES"
(57 rows)
================================================
FILE: drivers/testdata/sqlserver.listSchemas.expected.txt
================================================
List of schemas
Schema | Catalog
--------+---------
dbo | sakila
guest | sakila
(2 rows)
================================================
FILE: drivers/testdata/sqlserver.listTables.expected.txt
================================================
List of relations
Schema | Name | Type | Rows | Size | Comment
--------+---------------+------------+------+------+---------
dbo | film | BASE TABLE | 0 | |
dbo | film_actor | BASE TABLE | 0 | |
dbo | film_category | BASE TABLE | 0 | |
dbo | film_text | BASE TABLE | 0 | |
dbo | film_list | VIEW | 0 | |
(5 rows)
================================================
FILE: drivers/testdata/trino.descTable.expected.txt
================================================
BASE TABLE "sf1.orders"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
---------------+-------------+----------+---------+------+----------------+-------+--------------
orderkey | bigint | "YES" | | 0 | 0 | 0 | 0
custkey | bigint | "YES" | | 0 | 0 | 0 | 0
orderstatus | varchar(1) | "YES" | | 0 | 0 | 0 | 0
totalprice | double | "YES" | | 0 | 0 | 0 | 0
orderdate | date | "YES" | | 0 | 0 | 0 | 0
orderpriority | varchar(15) | "YES" | | 0 | 0 | 0 | 0
clerk | varchar(15) | "YES" | | 0 | 0 | 0 | 0
shippriority | integer | "YES" | | 0 | 0 | 0 | 0
comment | varchar(79) | "YES" | | 0 | 0 | 0 | 0
BASE TABLE "sf100.orders"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
---------------+-------------+----------+---------+------+----------------+-------+--------------
orderkey | bigint | "YES" | | 0 | 0 | 0 | 0
custkey | bigint | "YES" | | 0 | 0 | 0 | 0
orderstatus | varchar(1) | "YES" | | 0 | 0 | 0 | 0
totalprice | double | "YES" | | 0 | 0 | 0 | 0
orderdate | date | "YES" | | 0 | 0 | 0 | 0
orderpriority | varchar(15) | "YES" | | 0 | 0 | 0 | 0
clerk | varchar(15) | "YES" | | 0 | 0 | 0 | 0
shippriority | integer | "YES" | | 0 | 0 | 0 | 0
comment | varchar(79) | "YES" | | 0 | 0 | 0 | 0
BASE TABLE "sf1000.orders"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
---------------+-------------+----------+---------+------+----------------+-------+--------------
orderkey | bigint | "YES" | | 0 | 0 | 0 | 0
custkey | bigint | "YES" | | 0 | 0 | 0 | 0
orderstatus | varchar(1) | "YES" | | 0 | 0 | 0 | 0
totalprice | double | "YES" | | 0 | 0 | 0 | 0
orderdate | date | "YES" | | 0 | 0 | 0 | 0
orderpriority | varchar(15) | "YES" | | 0 | 0 | 0 | 0
clerk | varchar(15) | "YES" | | 0 | 0 | 0 | 0
shippriority | integer | "YES" | | 0 | 0 | 0 | 0
comment | varchar(79) | "YES" | | 0 | 0 | 0 | 0
BASE TABLE "sf10000.orders"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
---------------+-------------+----------+---------+------+----------------+-------+--------------
orderkey | bigint | "YES" | | 0 | 0 | 0 | 0
custkey | bigint | "YES" | | 0 | 0 | 0 | 0
orderstatus | varchar(1) | "YES" | | 0 | 0 | 0 | 0
totalprice | double | "YES" | | 0 | 0 | 0 | 0
orderdate | date | "YES" | | 0 | 0 | 0 | 0
orderpriority | varchar(15) | "YES" | | 0 | 0 | 0 | 0
clerk | varchar(15) | "YES" | | 0 | 0 | 0 | 0
shippriority | integer | "YES" | | 0 | 0 | 0 | 0
comment | varchar(79) | "YES" | | 0 | 0 | 0 | 0
BASE TABLE "sf100000.orders"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
---------------+-------------+----------+---------+------+----------------+-------+--------------
orderkey | bigint | "YES" | | 0 | 0 | 0 | 0
custkey | bigint | "YES" | | 0 | 0 | 0 | 0
orderstatus | varchar(1) | "YES" | | 0 | 0 | 0 | 0
totalprice | double | "YES" | | 0 | 0 | 0 | 0
orderdate | date | "YES" | | 0 | 0 | 0 | 0
orderpriority | varchar(15) | "YES" | | 0 | 0 | 0 | 0
clerk | varchar(15) | "YES" | | 0 | 0 | 0 | 0
shippriority | integer | "YES" | | 0 | 0 | 0 | 0
comment | varchar(79) | "YES" | | 0 | 0 | 0 | 0
BASE TABLE "sf300.orders"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
---------------+-------------+----------+---------+------+----------------+-------+--------------
orderkey | bigint | "YES" | | 0 | 0 | 0 | 0
custkey | bigint | "YES" | | 0 | 0 | 0 | 0
orderstatus | varchar(1) | "YES" | | 0 | 0 | 0 | 0
totalprice | double | "YES" | | 0 | 0 | 0 | 0
orderdate | date | "YES" | | 0 | 0 | 0 | 0
orderpriority | varchar(15) | "YES" | | 0 | 0 | 0 | 0
clerk | varchar(15) | "YES" | | 0 | 0 | 0 | 0
shippriority | integer | "YES" | | 0 | 0 | 0 | 0
comment | varchar(79) | "YES" | | 0 | 0 | 0 | 0
BASE TABLE "sf3000.orders"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
---------------+-------------+----------+---------+------+----------------+-------+--------------
orderkey | bigint | "YES" | | 0 | 0 | 0 | 0
custkey | bigint | "YES" | | 0 | 0 | 0 | 0
orderstatus | varchar(1) | "YES" | | 0 | 0 | 0 | 0
totalprice | double | "YES" | | 0 | 0 | 0 | 0
orderdate | date | "YES" | | 0 | 0 | 0 | 0
orderpriority | varchar(15) | "YES" | | 0 | 0 | 0 | 0
clerk | varchar(15) | "YES" | | 0 | 0 | 0 | 0
shippriority | integer | "YES" | | 0 | 0 | 0 | 0
comment | varchar(79) | "YES" | | 0 | 0 | 0 | 0
BASE TABLE "sf30000.orders"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
---------------+-------------+----------+---------+------+----------------+-------+--------------
orderkey | bigint | "YES" | | 0 | 0 | 0 | 0
custkey | bigint | "YES" | | 0 | 0 | 0 | 0
orderstatus | varchar(1) | "YES" | | 0 | 0 | 0 | 0
totalprice | double | "YES" | | 0 | 0 | 0 | 0
orderdate | date | "YES" | | 0 | 0 | 0 | 0
orderpriority | varchar(15) | "YES" | | 0 | 0 | 0 | 0
clerk | varchar(15) | "YES" | | 0 | 0 | 0 | 0
shippriority | integer | "YES" | | 0 | 0 | 0 | 0
comment | varchar(79) | "YES" | | 0 | 0 | 0 | 0
BASE TABLE "tiny.orders"
Name | Type | Nullable | Default | Size | Decimal Digits | Radix | Octet Length
---------------+-------------+----------+---------+------+----------------+-------+--------------
orderkey | bigint | "YES" | | 0 | 0 | 0 | 0
custkey | bigint | "YES" | | 0 | 0 | 0 | 0
orderstatus | varchar(1) | "YES" | | 0 | 0 | 0 | 0
totalprice | double | "YES" | | 0 | 0 | 0 | 0
orderdate | date | "YES" | | 0 | 0 | 0 | 0
orderpriority | varchar(15) | "YES" | | 0 | 0 | 0 | 0
clerk | varchar(15) | "YES" | | 0 | 0 | 0 | 0
shippriority | integer | "YES" | | 0 | 0 | 0 | 0
comment | varchar(79) | "YES" | | 0 | 0 | 0 | 0
================================================
FILE: drivers/testdata/trino.listSchemas.expected.txt
================================================
List of schemas
Schema | Catalog
----------+---------
sf1 | tpch
sf100 | tpch
sf1000 | tpch
sf10000 | tpch
sf100000 | tpch
sf300 | tpch
sf3000 | tpch
sf30000 | tpch
tiny | tpch
(9 rows)
================================================
FILE: drivers/testdata/trino.listTables.expected.txt
================================================
List of relations
Schema | Name | Type | Rows | Size | Comment
----------+--------+------------+------+------+---------
sf1 | orders | BASE TABLE | 0 | |
sf100 | orders | BASE TABLE | 0 | |
sf1000 | orders | BASE TABLE | 0 | |
sf10000 | orders | BASE TABLE | 0 | |
sf100000 | orders | BASE TABLE | 0 | |
sf300 | orders | BASE TABLE | 0 | |
sf3000 | orders | BASE TABLE | 0 | |
sf30000 | orders | BASE TABLE | 0 | |
tiny | orders | BASE TABLE | 0 | |
(9 rows)
================================================
FILE: drivers/trino/reader.go
================================================
package trino
import (
"database/sql"
"fmt"
"strings"
"github.com/xo/usql/drivers/metadata"
)
type metaReader struct {
metadata.LoggingReader
}
var _ metadata.CatalogReader = &metaReader{}
var _ metadata.ColumnStatReader = &metaReader{}
func (r metaReader) Catalogs(metadata.Filter) (*metadata.CatalogSet, error) {
qstr := `SHOW catalogs`
rows, closeRows, err := r.Query(qstr)
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.Catalog{}
for rows.Next() {
rec := metadata.Catalog{}
err = rows.Scan(&rec.Catalog)
if err != nil {
return nil, err
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewCatalogSet(results), nil
}
func (r metaReader) ColumnStats(f metadata.Filter) (*metadata.ColumnStatSet, error) {
names := []string{}
if f.Catalog != "" {
names = append(names, f.Catalog+".")
}
if f.Schema != "" {
names = append(names, f.Schema+".")
}
names = append(names, f.Parent)
rows, closeRows, err := r.Query(fmt.Sprintf("SHOW STATS FOR %s", strings.Join(names, "")))
if err != nil {
return nil, err
}
defer closeRows()
results := []metadata.ColumnStat{}
for rows.Next() {
rec := metadata.ColumnStat{Catalog: f.Catalog, Schema: f.Schema, Table: f.Parent}
name := sql.NullString{}
avgWidth := sql.NullInt32{}
numDistinct := sql.NullInt64{}
nullFrac := sql.NullFloat64{}
numRows := sql.NullInt64{}
min := sql.NullString{}
max := sql.NullString{}
err = rows.Scan(
&name,
&avgWidth,
&numDistinct,
&nullFrac,
&numRows,
&min,
&max,
)
if err != nil {
return nil, err
}
if !name.Valid {
continue
}
rec.Name = name.String
if avgWidth.Valid {
rec.AvgWidth = int(avgWidth.Int32)
}
if numDistinct.Valid {
rec.NumDistinct = numDistinct.Int64
}
if nullFrac.Valid {
rec.NullFrac = nullFrac.Float64
}
if min.Valid {
rec.Min = min.String
}
if max.Valid {
rec.Max = max.String
}
results = append(results, rec)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return metadata.NewColumnStatSet(results), nil
}
================================================
FILE: drivers/trino/trino.go
================================================
// Package trino defines and registers usql's Trino driver.
//
// See: https://github.com/trinodb/trino-go-client
package trino
import (
"context"
"io"
_ "github.com/trinodb/trino-go-client/trino" // DRIVER
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
infos "github.com/xo/usql/drivers/metadata/informationschema"
)
func init() {
newReader := func(db drivers.DB, opts ...metadata.ReaderOption) metadata.Reader {
ir := infos.New(
infos.WithPlaceholder(func(int) string { return "?" }),
infos.WithCustomClauses(map[infos.ClauseName]string{
infos.ColumnsColumnSize: "0",
infos.ColumnsNumericScale: "0",
infos.ColumnsNumericPrecRadix: "0",
infos.ColumnsCharOctetLength: "0",
}),
infos.WithFunctions(false),
infos.WithSequences(false),
infos.WithIndexes(false),
infos.WithConstraints(false),
infos.WithColumnPrivileges(false),
infos.WithUsagePrivileges(false),
)(db, opts...)
mr := &metaReader{
LoggingReader: metadata.NewLoggingReader(db, opts...),
}
return metadata.NewPluginReader(ir, mr)
}
drivers.Register("trino", drivers.Driver{
AllowMultilineComments: true,
Process: drivers.StripTrailingSemicolon,
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
err := db.QueryRowContext(
ctx,
`SELECT node_version FROM system.runtime.nodes LIMIT 1`,
).Scan(&ver)
if err != nil {
return "", err
}
return "Trino " + ver, nil
},
NewMetadataReader: newReader,
NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer {
return metadata.NewDefaultWriter(newReader(db, opts...))(db, w)
},
Copy: drivers.CopyWithInsert(func(int) string { return "?" }),
})
}
================================================
FILE: drivers/vertica/vertica.go
================================================
// Package vertica defines and registers usql's Vertica driver.
//
// See: https://github.com/vertica/vertica-sql-go
package vertica
import (
"context"
"crypto/tls"
"crypto/x509"
"database/sql"
"errors"
"io"
"net/url"
"os"
"regexp"
"strings"
vertica "github.com/vertica/vertica-sql-go" // DRIVER
"github.com/vertica/vertica-sql-go/logger"
"github.com/xo/dburl"
"github.com/xo/usql/drivers"
)
func init() {
// turn off logging
if os.Getenv("VERTICA_SQL_GO_LOG_LEVEL") == "" {
logger.SetLogLevel(logger.NONE)
}
errCodeRE := regexp.MustCompile(`(?i)^\[([0-9a-z]+)\]\s+(.+)`)
drivers.Register("vertica", drivers.Driver{
AllowDollar: true,
AllowMultilineComments: true,
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
if err := db.QueryRowContext(ctx, `SELECT version()`).Scan(&ver); err != nil {
return "", err
}
return ver, nil
},
Open: func(_ context.Context, u *dburl.URL, stdout, stderr func() io.Writer) (func(string, string) (*sql.DB, error), error) {
return func(driver, dsn string) (*sql.DB, error) {
u, err := url.Parse(dsn)
if err != nil {
return nil, err
}
q := u.Query()
if name := q.Get("ca_path"); name != "" {
if q.Get("tlsmode") != "server-strict" {
return nil, errors.New("tlsmode must be set to server-strict: ca_path is set")
}
cfg := &tls.Config{
ServerName: u.Hostname(),
}
if err := addCA(name, cfg); err != nil {
return nil, err
}
if err := vertica.RegisterTLSConfig("custom_tls_config", cfg); err != nil {
return nil, err
}
q.Set("tlsmode", "custom_tls_config")
}
return sql.Open(driver, u.String())
}, nil
},
ChangePassword: func(db drivers.DB, user, newpw, _ string) error {
_, err := db.Exec(`ALTER USER ` + user + ` IDENTIFIED BY '` + newpw + `'`)
return err
},
Err: func(err error) (string, string) {
msg := strings.TrimSpace(strings.TrimPrefix(err.Error(), "Error:"))
if m := errCodeRE.FindAllStringSubmatch(msg, -1); m != nil {
return m[0][1], strings.TrimSpace(m[0][2])
}
return "", msg
},
IsPasswordErr: func(err error) bool {
return strings.HasSuffix(strings.TrimSpace(err.Error()), "Invalid username or password")
},
})
}
// addCA adds the specified file name as a ca to the tls config.
func addCA(name string, cfg *tls.Config) error {
pool := x509.NewCertPool()
switch pem, err := os.ReadFile(name); {
case err != nil:
return err
case !pool.AppendCertsFromPEM(pem):
return errors.New("failed to append pem to cert pool")
}
cfg.RootCAs = pool
return nil
}
================================================
FILE: drivers/voltdb/voltdb.go
================================================
// Package voltdb defines and registers usql's VoltDB driver.
//
// See: https://github.com/VoltDB/voltdb-client-go
package voltdb
import (
_ "github.com/VoltDB/voltdb-client-go/voltdbclient" // DRIVER
"github.com/xo/usql/drivers"
)
func init() {
drivers.Register("voltdb", drivers.Driver{
AllowMultilineComments: true,
})
}
================================================
FILE: drivers/ydb/ydb.go
================================================
// Package ydb defines and registers usql's YDB driver.
//
// See: https://github.com/ydb-platform/ydb-go-sdk
package ydb
import (
"context"
"errors"
"strconv"
"github.com/xo/usql/drivers"
"github.com/ydb-platform/ydb-go-sdk/v3" // DRIVER
)
func init() {
drivers.Register("ydb", drivers.Driver{
Version: func(ctx context.Context, db drivers.DB) (string, error) {
var ver string
if err := db.QueryRowContext(ctx, `SELECT '' AS version`).Scan(&ver); err != nil {
return "", err
}
return "YDB " + ver, nil
},
Err: func(err error) (string, string) {
var e ydb.Error
if errors.As(err, &e) {
return strconv.Itoa(int(e.Code())), e.Error()
}
return "", err.Error()
},
})
}
================================================
FILE: env/env.go
================================================
// Package env contains runtime environment variables for usql, along with
// various helper funcs to determine the user's configuration.
package env
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"os/user"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"github.com/kenshaw/rasterm"
"github.com/xo/dburl/passfile"
"github.com/xo/usql/text"
)
// vars are environment variables.
var vars *Variables
func init() {
vars = NewDefaultVars()
}
// Vars returns the environment variables.
func Vars() *Variables {
return vars
}
// Get returns a standard variable.
func Get(name string) string {
value, _ := vars.Get(name)
return value
}
// Getenv tries retrieving successive keys from os environment variables.
func Getenv(keys ...string) (string, bool) {
m := make(map[string]string)
for _, v := range os.Environ() {
if i := strings.Index(v, "="); i != -1 {
m[v[:i]] = v[i+1:]
}
}
for _, key := range keys {
if v, ok := m[key]; ok {
return v, true
}
}
return "", false
}
// Chdir changes the current working directory to the specified path, or to the
// user's home directory if path is not specified.
func Chdir(u *user.User, path string) error {
if path != "" {
path = passfile.Expand(u.HomeDir, path)
} else {
path = u.HomeDir
}
return os.Chdir(path)
}
// OpenFile opens a file for read (os.O_RDONLY), returning the full, expanded
// path of the file. Callers are responsible for closing the returned file.
func OpenFile(u *user.User, path string) (string, *os.File, error) {
path, err := filepath.EvalSymlinks(passfile.Expand(u.HomeDir, path))
switch {
case err != nil && os.IsNotExist(err):
return "", nil, text.ErrNoSuchFileOrDirectory
case err != nil:
return "", nil, err
}
fi, err := os.Stat(path)
switch {
case err != nil && os.IsNotExist(err):
return "", nil, text.ErrNoSuchFileOrDirectory
case err != nil:
return "", nil, err
case fi.IsDir():
return "", nil, text.ErrCannotIncludeDirectories
}
f, err := os.OpenFile(path, os.O_RDONLY, 0)
if err != nil {
return "", nil, err
}
return path, f, nil
}
// EditFile edits a file. If path is empty, then a temporary file will be
// created.
func EditFile(u *user.User, path, line string, buf []byte) ([]byte, error) {
ed, _ := vars.Get("EDITOR")
switch {
case ed == "":
if ed, _ = exec.LookPath("vi"); ed == "" {
return nil, text.ErrNoEditorDefined
}
case path != "":
path = passfile.Expand(u.HomeDir, path)
default:
f, err := os.CreateTemp("", text.CommandLower()+".*.sql")
if err != nil {
return nil, err
}
path = f.Name()
if _, err = f.Write(append(bytes.TrimSuffix(buf, lineend), '\n')); err != nil {
f.Close()
return nil, err
}
if err = f.Close(); err != nil {
return nil, err
}
}
// setup args
args := []string{path}
if line != "" {
if s, ok := Getenv(text.CommandUpper() + "_EDITOR_LINENUMBER_ARG"); ok {
args = append(args, s+line)
} else {
args = append(args, "+"+line)
}
}
// create command
c := exec.Command(ed, args...)
c.Stdin, c.Stdout, c.Stderr = os.Stdin, os.Stdout, os.Stderr
if err := c.Run(); err != nil {
return nil, err
}
// read
buf, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return bytes.TrimSuffix(buf, lineend), nil
}
// HistoryFile returns the path to the history file.
//
// Defaults to ~/._history, overridden by environment variable
// _HISTORY (ie, ~/.usql_history and USQL_HISTORY).
func HistoryFile(u *user.User) string {
n := text.CommandUpper() + "_HISTORY"
path := "~/." + strings.ToLower(n)
if s, ok := Getenv(n); ok {
path = s
}
return passfile.Expand(u.HomeDir, path)
}
// RCFile returns the path to the RC file.
//
// Defaults to ~/.rc, overridden by environment variable
// RC (ie, ~/.usqlrc and USQLRC).
func RCFile(u *user.User) string {
n := text.CommandUpper() + "RC"
path := "~/." + strings.ToLower(n)
if s, ok := Getenv(n); ok {
path = s
}
return passfile.Expand(u.HomeDir, path)
}
// Getshell returns the user's defined SHELL, or system default (if found on
// path) and the appropriate command-line argument for the returned shell.
//
// Looks at the SHELL environment variable first, and then COMSPEC/ComSpec on
// Windows. Defaults to sh on non-Windows systems, and to cmd.exe on Windows.
func Getshell() (string, string) {
shell, ok := Getenv("SHELL")
param := "-c"
if !ok && runtime.GOOS == "windows" {
shell, _ = Getenv("COMSPEC", "ComSpec")
param = "/c"
}
// look up path for "cmd.exe" if no other SHELL
if shell == "" && runtime.GOOS == "windows" {
shell, _ = exec.LookPath("cmd.exe")
if shell != "" {
param = "/c"
}
}
// lookup path for "sh" if no other SHELL
if shell == "" {
shell, _ = exec.LookPath("sh")
if shell != "" {
param = "-c"
}
}
return shell, param
}
// Shell runs s as a shell. When s is empty the user's SHELL or COMSPEC is
// used. See Getshell.
func Shell(s string) error {
shell, param := Getshell()
if shell == "" {
return text.ErrNoShellAvailable
}
s = strings.TrimSpace(s)
var params []string
if s != "" {
params = append(params, param, s)
}
// drop to shell
cmd := exec.Command(shell, params...)
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
_ = cmd.Run()
return nil
}
// Pipe starts a command and returns its input for writing.
func Pipe(stdout, stderr io.Writer, c string) (io.WriteCloser, *exec.Cmd, error) {
shell, param := Getshell()
if shell == "" {
return nil, nil, text.ErrNoShellAvailable
}
cmd := exec.Command(shell, param, c)
cmd.Stdout, cmd.Stderr = stdout, stderr
out, err := cmd.StdinPipe()
if err != nil {
return nil, nil, err
}
return out, cmd, cmd.Start()
}
// Exec executes s using the user's SHELL / COMSPEC with -c (or /c) and
// returning the captured output. See Getshell.
//
// When SHELL or COMSPEC is not defined, then "sh" / "cmd.exe" will be used
// instead, assuming it is found on the system's PATH.
func Exec(s string) (string, error) {
s = strings.TrimSpace(s)
if s == "" {
return "", nil
}
shell, param := Getshell()
if shell == "" {
return "", text.ErrNoShellAvailable
}
buf, err := exec.Command(shell, param, s).CombinedOutput()
if err != nil {
return "", err
}
// remove ending \r\n
buf = bytes.TrimSuffix(buf, lineend)
buf = bytes.TrimSuffix(buf, []byte{'\r'})
return string(buf), nil
}
// Unquote unquotes a string.
func Unquote(s string) (string, error) {
switch n := len(s); {
case n == 0:
return "", nil
case n < 2, s[n-1] != s[0], s[0] != '\'' && s[0] != '"' && s[0] != '`':
return "", text.ErrUnterminatedQuotedString
}
quote := s[0]
s = s[1 : len(s)-1]
if quote == '\'' {
s = cleanDoubleRE.ReplaceAllString(s, `$1\'`)
}
// this is the last part of strconv.Unquote
var runeTmp [utf8.UTFMax]byte
buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations.
for len(s) > 0 {
c, multibyte, ss, err := strconv.UnquoteChar(s, quote)
switch {
case err != nil && err == strconv.ErrSyntax:
return "", text.ErrInvalidQuotedString
case err != nil:
return "", err
}
s = ss
if c < utf8.RuneSelf || !multibyte {
buf = append(buf, byte(c))
} else {
n := utf8.EncodeRune(runeTmp[:], c)
buf = append(buf, runeTmp[:n]...)
}
}
return string(buf), nil
}
// Untick returns a func that unquotes and unticks strings for the user.
//
// When exec is true, backtick'd strings will be executed using the provided
// user's shell (see Exec).
func Untick(u *user.User, v *Variables, exec bool) func(string, bool) (string, bool, error) {
return func(s string, isvar bool) (string, bool, error) {
// fmt.Fprintf(os.Stderr, "untick: %q\n", s)
switch {
case isvar:
value, ok := v.Get(s)
return value, ok, nil
case len(s) < 2:
return "", false, text.ErrInvalidQuotedString
}
c := s[0]
z, err := Unquote(s)
switch {
case err != nil:
return "", false, err
case c == '\'', c == '"':
return z, true, nil
case c != '`':
return "", false, text.ErrInvalidQuotedString
case !exec:
return z, true, nil
}
res, err := Exec(z)
if err != nil {
return "", false, err
}
return res, true, nil
}
}
// Quote quotes a string.
func Quote(s string) string {
s = strconv.QuoteToGraphic(s)
return "'" + s[1:len(s)-1] + "'"
}
// TermGraphics returns the [rasterm.TermType] based on TERM_GRAPHICS
// environment variable.
func TermGraphics() rasterm.TermType {
var typ rasterm.TermType
s, _ := vars.Get("TERM_GRAPHICS")
_ = typ.UnmarshalText([]byte(s))
return typ
}
// ValidIdentifier returns an error when n is not a valid identifier.
func ValidIdentifier(n string) error {
r := []rune(n)
rlen := len(r)
if rlen < 1 {
return text.ErrInvalidIdentifier
}
for i := 0; i < rlen; i++ {
if c := r[i]; c != '_' && !unicode.IsLetter(c) && !unicode.IsNumber(c) {
return text.ErrInvalidIdentifier
}
}
return nil
}
func ParseBool(value, name string) (string, error) {
switch strings.ToLower(value) {
case "1", "t", "tr", "tru", "true", "on":
return "on", nil
case "0", "f", "fa", "fal", "fals", "false", "of", "off":
return "off", nil
}
return "", fmt.Errorf(text.FormatFieldInvalidValue, value, name, "Boolean")
}
func ParseKeywordBool(value, name string, keywords ...string) (string, error) {
v := strings.ToLower(value)
switch v {
case "1", "t", "tr", "tru", "true", "on":
return "on", nil
case "0", "f", "fa", "fal", "fals", "false", "of", "off":
return "off", nil
}
for _, k := range keywords {
if v == k {
return v, nil
}
}
return "", fmt.Errorf(text.FormatFieldInvalid, value, name)
}
// lineend is the line ending.
var lineend = []byte{'\n'}
// cleanDoubleRE matches double quotes.
var cleanDoubleRE = regexp.MustCompile(`(^|[^\\])''`)
================================================
FILE: env/list.go
================================================
package env
import (
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"unicode"
"github.com/xo/usql/text"
"github.com/yookoala/realpath"
)
// Listing writes a formatted listing of the special environment variables to
// w, separated in sections based on variable type.
func Listing(w io.Writer) error {
varsWithDesc := make([]string, len(varNames))
for i, v := range varNames {
varsWithDesc[i] = v.String()
}
pvarsWithDesc := make([]string, len(pvarNames))
for i, v := range pvarNames {
pvarsWithDesc[i] = v.String()
}
// determine config dir name
configDir, configExtra := buildConfigDir("config.yaml")
// environment var names
configDesc := configDir
if configExtra != "" {
configDesc = configExtra
}
ev := []varName{
{
text.CommandUpper() + "_CONFIG",
fmt.Sprintf(`config file path (default %q)`, configDesc),
},
}
envVarsWithDesc := make([]string, len(envVarNames)+1)
for i, v := range append(ev, envVarNames...) {
envVarsWithDesc[i] = v.String()
}
if configExtra != "" {
configExtra = " (" + configExtra + ")"
}
fmt.Fprintf(
w,
variableTpl,
text.CommandName,
strings.TrimRightFunc(strings.Join(varsWithDesc, ""), unicode.IsSpace),
strings.TrimRightFunc(strings.Join(pvarsWithDesc, ""), unicode.IsSpace),
strings.TrimRightFunc(strings.Join(envVarsWithDesc, ""), unicode.IsSpace),
configDir,
configExtra,
)
return nil
}
func buildConfigDir(configName string) (string, string) {
dir := `$HOME/.config/usql`
switch runtime.GOOS {
case "darwin":
dir = `$HOME/Library/Application Support`
case "windows":
dir = `%AppData%\usql`
}
configDir, err := os.UserConfigDir()
if err != nil {
return filepath.Join(dir, configName), ""
}
if configDir, err = realpath.Realpath(configDir); err != nil {
return filepath.Join(dir, configName), ""
}
return filepath.Join(dir, configName), filepath.Join(configDir, "usql", configName)
}
type varName struct {
name string
desc string
}
func (v varName) String() string {
return fmt.Sprintf(" %s\n %s\n", v.name, v.desc)
}
var varNames = []varName{
{
`ECHO_HIDDEN`,
`if set, display internal queries executed by backslash commands; if set to "noexec", shows queries without execution`,
},
{
`ON_ERROR_STOP`,
`stop batch execution after error`,
},
{
`PROMPT1`,
`specifies the standard ` + text.CommandName + ` prompt`,
},
{
`QUIET`,
`run quietly (same as -q option)`,
},
{
`ROW_COUNT`,
`number of rows returned or affected by last query, or 0`,
},
}
var (
formatRE = regexp.MustCompile(`^(unaligned|aligned|wrapped|html|asciidoc|latex|latex-longtable|troff-ms|csv|json|vertical)$`)
linestlyeRE = regexp.MustCompile(`^(ascii|old-ascii|unicode)$`)
borderRE = regexp.MustCompile(`^(single|double)$`)
)
var pvarNames = []varName{
{
`border`,
`border style (number)`,
},
{
`columns`,
`target width for the wrapped format`,
},
{
`csv_fieldsep`,
`field separator for CSV output (default ",")`,
},
{
`expanded`,
`expanded output [on, off, auto]`,
},
{
`fieldsep`,
`field separator for unaligned output (default "|")`,
},
{
`fieldsep_zero`,
`set field separator for unaligned output to a zero byte`,
},
{
`footer`,
`enable or disable display of the table footer [on, off]`,
},
{
`format`,
`set output format [unaligned, aligned, wrapped, vertical, html, asciidoc, csv, json, ...]`,
},
{
`linestyle`,
`set the border line drawing style [ascii, old-ascii, unicode]`,
},
{
`null`,
`set the string to be printed in place of a null value`,
},
{
`numericlocale`,
`enable display of a locale-specific character to separate groups of digits`,
},
{
`pager_min_lines`,
`minimum number of lines required in the output to use a pager, 0 to disable (default 0)`,
},
{
`pager`,
`control when an external pager is used [on, off, always]`,
},
{
`recordsep`,
`record (line) separator for unaligned output`,
},
{
`recordsep_zero`,
`set record separator for unaligned output to a zero byte`,
},
{
`tableattr`,
`specify attributes for table tag in html format, or proportional column widths for left-aligned data types in latex-longtable format`,
},
{
`time`,
`format used to display time/date column values (default RFC3339Nano)`,
},
{
`timezone`,
`the timezone to display dates in (default "")`,
},
{
`title`,
`set the table title for subsequently printed tables`,
},
{
`tuples_only`,
`if set, only actual table data is shown`,
},
{
`unicode_border_linestyle`,
`set the style of Unicode line drawing [single, double]`,
},
{
`unicode_column_linestyle`,
`set the style of Unicode line drawing [single, double]`,
},
{
`unicode_header_linestyle`,
`set the style of Unicode line drawing [single, double]`,
},
}
var envVarNames = []varName{
{
text.CommandUpper() + `_EDITOR, EDITOR, VISUAL`,
`editor used by the \e, \ef, and \ev commands`,
},
{
text.CommandUpper() + `_EDITOR_LINENUMBER_ARG`,
`how to specify a line number when invoking the editor`,
},
{
text.CommandUpper() + `_HISTORY`,
`alternative location for the command history file`,
},
{
text.CommandUpper() + `_PAGER, PAGER`,
`name of external pager program`,
},
{
text.CommandUpper() + `_SHOW_HOST_INFORMATION`,
`display host information when connecting to a database`,
},
{
text.CommandUpper() + `RC`,
`alternative location for the user's .usqlrc file`,
},
{
text.CommandUpper() + `_SSLMODE, SSLMODE`,
`when set to 'retry', allows connections to attempt to reconnect when no ?sslmode= was specified on the url`,
},
{
`SYNTAX_HL`,
`enable syntax highlighting`,
},
{
`SYNTAX_HL_FORMAT`,
`chroma library formatter name`,
},
{
`SYNTAX_HL_STYLE`,
`chroma library style name (default "monokai")`,
},
{
`SYNTAX_HL_OVERRIDE_BG`,
`enables overriding the background color of the chroma styles`,
},
{
`TERM_GRAPHICS`,
`use the specified terminal graphics`,
},
{
`SHELL`,
`shell used by the \! command`,
},
}
const variableTpl = `List of specially treated variables
%s variables:
Usage:
%[1]s --set=NAME=VALUE
or \set NAME VALUE inside %[1]s
%[2]s
Display settings:
Usage:
%[1]s --pset=NAME[=VALUE]
or \pset NAME [VALUE] inside %[1]s
%[3]s
Environment variables:
Usage:
NAME=VALUE [NAME=VALUE] %[1]s ...
or \setenv NAME [VALUE] inside %[1]s
%[4]s
Connection variables:
Usage:
%[1]s --cset NAME[=DSN]
or \cset NAME [DSN] inside %[1]s
or \cset NAME DRIVER PARAMS... inside %[1]s
or define in %[5]s%[6]s
`
================================================
FILE: env/vars.go
================================================
package env
import (
"fmt"
"io"
"maps"
"os/exec"
"slices"
"strconv"
"strings"
"time"
syslocale "github.com/jeandeaual/go-locale"
"github.com/xo/terminfo"
"github.com/xo/usql/text"
)
// Variables handles the standard, print, and connection variables.
type Variables struct {
// vars holds standard variables.
vars map[string]string
// prnt holds print variables ("print" is a reserved word).
prnt map[string]string
// conn holds connection variables.
conn map[string][]string
}
// NewVars creates a set of empty variables.
func NewVars() *Variables {
return &Variables{
vars: make(map[string]string),
prnt: make(map[string]string),
conn: make(map[string][]string),
}
}
// NewDefaultVars creates standard, print, and connection variables, based on
// environment variables.
func NewDefaultVars() *Variables {
cmdNameUpper := strings.ToUpper(text.CommandName)
// get USQL_* variables
showHostInformation := "true"
if v, _ := Getenv(cmdNameUpper + "_SHOW_HOST_INFORMATION"); v != "" {
showHostInformation = v
}
// get NO_COLOR
noColor := false
if s, ok := Getenv("NO_COLOR"); ok {
noColor = s != "0" && s != "false" && s != "off"
}
// get color level
colorLevel, _ := terminfo.ColorLevelFromEnv()
enableSyntaxHL := "true"
if noColor || colorLevel < terminfo.ColorLevelBasic {
enableSyntaxHL = "false"
}
// pager
pagerCmd, ok := Getenv(cmdNameUpper+"_PAGER", "PAGER")
pager := "off"
if !ok {
for _, s := range []string{"less", "more"} {
if _, err := exec.LookPath(s); err == nil {
pagerCmd = s
break
}
}
}
if pagerCmd != "" {
pager = "on"
}
// editor
editorCmd, _ := Getenv(cmdNameUpper+"_EDITOR", "EDITOR", "VISUAL")
// sslmode
sslmode, ok := Getenv(cmdNameUpper+"_SSLMODE", "SSLMODE")
if !ok {
sslmode = "retry"
}
// determine locale
locale := "en-US"
if s, err := syslocale.GetLocale(); err == nil {
locale = s
}
return &Variables{
vars: map[string]string{
// usql related logic
"SHOW_HOST_INFORMATION": showHostInformation,
"PAGER": pagerCmd,
"EDITOR": editorCmd,
"QUIET": "off",
"ON_ERROR_STOP": "off",
// prompts
"PROMPT1": "%S%N%m%/%R%# ",
// syntax highlighting variables
"SYNTAX_HL": enableSyntaxHL,
"SYNTAX_HL_FORMAT": colorLevel.ChromaFormatterName(),
"SYNTAX_HL_STYLE": "monokai",
"SYNTAX_HL_OVERRIDE_BG": "true",
"SSLMODE": sslmode,
"TERM_GRAPHICS": "none",
},
prnt: map[string]string{
"border": "1",
"columns": "0",
"csv_fieldsep": ",",
"expanded": "off",
"fieldsep": "|",
"fieldsep_zero": "off",
"footer": "on",
"format": "aligned",
"linestyle": "ascii",
"locale": locale,
"null": "",
"numericlocale": "off",
"pager_min_lines": "0",
"pager": pager,
"recordsep": "\n",
"recordsep_zero": "off",
"tableattr": "",
"time": "RFC3339Nano",
"timezone": "",
"title": "",
"tuples_only": "off",
"unicode_border_linestyle": "single",
"unicode_column_linestyle": "single",
"unicode_header_linestyle": "single",
},
conn: make(map[string][]string),
}
}
// Vars returns a copy of the standard variables.
func (v *Variables) Vars() map[string]string {
return maps.Clone(v.vars)
}
// Print returns a copy of the print variables.
func (v *Variables) Print() map[string]string {
return maps.Clone(v.prnt)
}
// Conn returns a copy of the connection variables.
func (v *Variables) Conn() map[string][]string {
return maps.Clone(v.conn)
}
// Get retrieves a standard variable.
func (v *Variables) Get(name string) (string, bool) {
value, ok := v.vars[name]
return value, ok
}
// Set sets a standard variable.
func (v *Variables) Set(name, value string) error {
if err := ValidIdentifier(name); err != nil {
return err
}
switch name {
case "ON_ERROR_STOP", "QUIET":
if value == "" {
value = "on"
} else {
var err error
if value, err = ParseBool(value, name); err != nil {
return err
}
}
}
v.vars[name] = value
return nil
}
// Unset unsets a standard variable.
func (v *Variables) Unset(name string) error {
if err := ValidIdentifier(name); err != nil {
return err
}
delete(v.vars, name)
return nil
}
// Dump dumps the standard variables to w.
func (v *Variables) Dump(w io.Writer) error {
for _, k := range slices.Sorted(maps.Keys(v.vars)) {
_, _ = fmt.Fprintln(w, k, "=", Quote(v.vars[k]))
}
return nil
}
// GetPrint returns a print variable.
func (v *Variables) GetPrint(name string) (string, error) {
if val, ok := v.prnt[name]; ok {
return val, nil
}
return "", fmt.Errorf(text.UnknownFormatFieldName, name)
}
// SetPrint sets a print variable.
func (v *Variables) SetPrint(name, value string) (string, error) {
if _, ok := v.prnt[name]; !ok {
return "", fmt.Errorf(text.UnknownFormatFieldName, name)
}
switch name {
case "border", "columns", "pager_min_lines":
i, _ := strconv.Atoi(value)
v.prnt[name] = fmt.Sprintf("%d", i)
case "pager":
s, err := ParseKeywordBool(value, name, "always")
if err != nil {
return "", text.ErrInvalidFormatPagerType
}
v.prnt[name] = s
case "expanded":
s, err := ParseKeywordBool(value, name, "auto")
if err != nil {
return "", text.ErrInvalidFormatExpandedType
}
v.prnt[name] = s
case "fieldsep_zero", "footer", "numericlocale", "recordsep_zero", "tuples_only":
s, err := ParseBool(value, name)
if err != nil {
return "", err
}
v.prnt[name] = s
case "format":
if !formatRE.MatchString(value) {
return "", text.ErrInvalidFormatType
}
v.prnt[name] = value
case "linestyle":
if !linestlyeRE.MatchString(value) {
return "", text.ErrInvalidFormatLineStyle
}
v.prnt[name] = value
case "csv_fieldsep", "fieldsep", "null", "recordsep", "tableattr", "time", "title", "locale":
v.prnt[name] = value
case "timezone":
if _, err := time.LoadLocation(value); err != nil {
return "", text.ErrInvalidTimezoneLocation
}
v.prnt[name] = value
case "unicode_border_linestyle", "unicode_column_linestyle", "unicode_header_linestyle":
if !borderRE.MatchString(value) {
return "", text.ErrInvalidFormatBorderLineStyle
}
v.prnt[name] = value
default:
panic(fmt.Sprintf("field %s was defined in the print variables, but not in switch", name))
}
return v.prnt[name], nil
}
// TogglePrint toggles a print variable.
func (v *Variables) TogglePrint(name, extra string) (string, error) {
if _, ok := v.prnt[name]; !ok {
return "", fmt.Errorf(text.UnknownFormatFieldName, name)
}
switch name {
case "border", "columns", "pager_min_lines":
case "pager":
switch v.prnt[name] {
case "on", "always":
v.prnt[name] = "off"
case "off":
v.prnt[name] = "on"
default:
panic(fmt.Sprintf("invalid state for field %s", name))
}
case "expanded":
switch v.prnt[name] {
case "on", "auto":
v.prnt[name] = "off"
case "off":
v.prnt[name] = "on"
default:
panic(fmt.Sprintf("invalid state for field %s", name))
}
case "fieldsep_zero", "footer", "numericlocale", "recordsep_zero", "tuples_only":
switch v.prnt[name] {
case "on":
v.prnt[name] = "off"
case "off":
v.prnt[name] = "on"
default:
panic(fmt.Sprintf("invalid state for field %s", name))
}
case "format":
switch {
case extra != "" && v.prnt[name] != extra:
v.prnt[name] = extra
case v.prnt[name] == "aligned":
v.prnt[name] = "unaligned"
default:
v.prnt[name] = "aligned"
}
case "linestyle":
case "csv_fieldsep", "fieldsep", "null", "recordsep", "time", "timezone", "locale":
case "tableattr", "title":
v.prnt[name] = ""
case "unicode_border_linestyle", "unicode_column_linestyle", "unicode_header_linestyle":
default:
panic(fmt.Sprintf("field %s was defined in the print variables, but not in switch", name))
}
return v.prnt[name], nil
}
// DumpPrint dumps the print variables to w.
func (v *Variables) DumpPrint(w io.Writer) error {
width, keys := 0, maps.Keys(v.prnt)
for k := range keys {
width = max(len(k), width)
}
for _, k := range slices.Sorted(keys) {
val := v.prnt[k]
switch k {
case "csv_fieldsep", "fieldsep", "recordsep", "null":
val = strconv.QuoteToASCII(val)
case "tableattr", "title":
if val != "" {
val = strconv.QuoteToASCII(val)
}
}
fmt.Fprintf(w, "%-*s %s\n", width, k, val)
// k+strings.Repeat(" ", width-len(k)), val)
}
return nil
}
// PrintTimeFormat returns the user's time format converted to Go's time.Format
// value.
func (v *Variables) PrintTimeFormat() string {
tfmt := v.prnt["time"]
if s, ok := timeConsts[tfmt]; ok {
return s
}
return tfmt
}
// SetConn sets a named connection variable.
func (v *Variables) SetConn(name string, vals ...string) error {
if err := ValidIdentifier(name); err != nil {
return err
}
if _, ok := v.conn[name]; len(vals) == 0 || vals[0] == "" && ok {
delete(v.conn, name)
} else {
v.conn[name] = slices.Clone(vals)
}
return nil
}
// GetConn returns a connection variable.
func (v *Variables) GetConn(name string) ([]string, bool) {
vals, ok := v.conn[name]
if !ok {
return nil, false
}
return slices.Clone(vals), true
}
// DumpConn dumps the connection variables to w.
func (v *Variables) DumpConn(w io.Writer) error {
for _, k := range slices.Sorted(maps.Keys(v.conn)) {
fmt.Fprintln(w, k, "=", Quote(strings.Join(v.conn[k], " ")))
}
return nil
}
// timeConsts are well known time consts.
var timeConsts = map[string]string{
"ANSIC": time.ANSIC,
"UnixDate": time.UnixDate,
"RubyDate": time.RubyDate,
"RFC822": time.RFC822,
"RFC822Z": time.RFC822Z,
"RFC850": time.RFC850,
"RFC1123": time.RFC1123,
"RFC1123Z": time.RFC1123Z,
"RFC3339": time.RFC3339,
"RFC3339Nano": time.RFC3339Nano,
"Kitchen": time.Kitchen,
"Stamp": time.Stamp,
"StampMilli": time.StampMilli,
"StampMicro": time.StampMicro,
"StampNano": time.StampNano,
}
/*
// Get retrieves a standard variable.
func (v *Vars) Get(s string) (string, bool, error) {
func (v *Vars) Unquote()
q, n := "", s
if c := s[0]; c == '\'' || c == '"' {
var err error
if n, err = Unquote(s); err != nil {
return "", false, err
}
q = string(c)
}
if val, ok := v.v[n]; ok {
return q + val + q, true, nil
}
return s, false, nil
*/
================================================
FILE: gen.go
================================================
//go:build ignore
package main
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"io/fs"
"os"
"path/filepath"
"regexp"
"slices"
"sort"
"strings"
"time"
"unicode"
"github.com/mattn/go-runewidth"
"github.com/xo/dburl"
"github.com/yookoala/realpath"
)
func main() {
licenseStart := flag.Int("license-start", 2015, "license start year")
licenseAuthor := flag.String("license-author", "Kenneth Shaw", "license author")
dburlGen := flag.Bool("dburl-gen", false, "enable dburl generation")
dburlDir := flag.String("dburl-dir", getDburlDir(), "dburl dir")
dburlLicenseStart := flag.Int("dburl-license-start", 2015, "dburl license start year")
flag.Parse()
if err := run(*licenseStart, *licenseAuthor, *dburlGen, *dburlDir, *dburlLicenseStart); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
func run(licenseStart int, licenseAuthor string, dburlGen bool, dburlDir string, dburlLicenseStart int) error {
wd, err := os.Getwd()
if err != nil {
return err
}
if err := loadDrivers(filepath.Join(wd, "drivers")); err != nil {
return err
}
if err := loadCommands(filepath.Join(wd, "metacmd", "cmds.go")); err != nil {
return err
}
if err := writeInternal(filepath.Join(wd, "internal"), baseDrivers, mostDrivers, allDrivers, badDrivers); err != nil {
return err
}
if err := writeReadme(wd, true); err != nil {
return err
}
if err := writeLicenseFiles(licenseStart, licenseAuthor); err != nil {
return err
}
if err := writeCommands(filepath.Join(wd, "metacmd", "descs.go")); err != nil {
return err
}
if dburlGen {
if err := writeReadme(dburlDir, false); err != nil {
return err
}
if err := writeDburlLicense(dburlDir, dburlLicenseStart, licenseAuthor); err != nil {
return err
}
}
return nil
}
// loadDrivers loads the driver descriptions.
func loadDrivers(wd string) error {
skipDirs := []string{"completer", "metadata"}
err := fs.WalkDir(os.DirFS(wd), ".", func(n string, d fs.DirEntry, err error) error {
switch {
case err != nil:
return err
case d.IsDir():
return nil
}
m := dirRE.FindAllStringSubmatch(n, -1)
if m == nil || m[0][1] != m[0][2] || slices.Contains(skipDirs, m[0][1]) {
return nil
}
tag, dest := m[0][1], mostDrivers
driver, err := parseDriverInfo(tag, filepath.Join(wd, n))
switch {
case err != nil:
return err
case driver.Group == "base":
dest = baseDrivers
case driver.Group == "most":
case driver.Group == "all":
dest = allDrivers
case driver.Group == "bad":
dest = badDrivers
default:
return fmt.Errorf("driver %s has invalid group %q", tag, driver.Group)
}
dest[tag] = driver
if dest[tag].Aliases != nil {
for _, alias := range dest[tag].Aliases {
wireDrivers[alias[0]] = DriverInfo{
Tag: tag,
Driver: alias[0],
Pkg: dest[tag].Pkg,
Desc: alias[1],
Wire: true,
}
}
}
return nil
})
return err
}
// loadCommands loads command descriptions.
func loadCommands(name string) error {
f, err := parser.ParseFile(token.NewFileSet(), name, nil, parser.ParseComments)
if err != nil {
return err
}
cmds = make(map[string][]desc)
for _, d := range f.Decls {
section, descs, ok, err := decodeCommand(d)
switch {
case err != nil:
return err
case !ok:
continue
}
cmds[section] = append(cmds[section], descs...)
}
// check all sections have at least one command
for _, section := range sections {
if descs, ok := cmds[section]; !ok || len(descs) == 0 {
return fmt.Errorf("section %q has no meta commands", section)
}
}
return nil
}
func writeInternal(wd string, drivers ...map[string]DriverInfo) error {
// build known build tags
var known []DriverInfo
for _, m := range drivers {
for _, v := range m {
known = append(known, v)
}
}
sort.Slice(known, func(i, j int) bool {
return known[i].Tag < known[j].Tag
})
knownStr := ""
for _, v := range known {
knownStr += fmt.Sprintf("\n%q: %q, // %s", v.Tag, v.Driver, v.Pkg)
}
// format and write internal.go
buf, err := format.Source([]byte(fmt.Sprintf(internalGo, knownStr)))
if err != nil {
return err
}
if err := os.WriteFile(filepath.Join(wd, "internal.go"), buf, 0o644); err != nil {
return err
}
// write .go
for _, v := range known {
var tags string
switch v.Group {
case "base":
tags = "(!no_base || " + v.Tag + ")"
case "most":
tags = "(all || most || " + v.Tag + ")"
case "all":
tags = "(all || " + v.Tag + ")"
case "bad":
tags = "(bad || " + v.Tag + ")"
default:
panic(v.Tag)
}
tags += " && !no_" + v.Tag
buf, err := format.Source([]byte(fmt.Sprintf(internalTagGo, tags, "github.com/xo/usql/drivers/"+v.Tag, v.Desc)))
if err != nil {
return err
}
if err := os.WriteFile(filepath.Join(wd, v.Tag+".go"), buf, 0o644); err != nil {
return err
}
}
return nil
}
func writeReadme(dir string, includeTagSummary bool) error {
readme := filepath.Join(dir, "README.md")
buf, err := os.ReadFile(readme)
if err != nil {
return err
}
start := bytes.Index(buf, []byte(driverTableStart))
end := bytes.Index(buf, []byte(driverTableEnd))
if start == -1 || end == -1 {
return errors.New("unable to find driver table start/end in README.md")
}
b := new(bytes.Buffer)
if _, err := b.Write(append(buf[:start+len(driverTableStart)], '\n', '\n')); err != nil {
return err
}
if _, err := b.Write(append([]byte(buildDriverTable(includeTagSummary)), '\n')); err != nil {
return err
}
if _, err := b.Write(buf[end:]); err != nil {
return err
}
return os.WriteFile(readme, b.Bytes(), 0o644)
}
func writeLicenseFiles(licenseStart int, licenseAuthor string) error {
s := fmt.Sprintf(license, licenseStart, time.Now().Year(), licenseAuthor)
if err := os.WriteFile("LICENSE", append([]byte(s), '\n'), 0o644); err != nil {
return err
}
textGo := fmt.Sprintf(licenseTextGo, s)
if err := os.WriteFile("text/license.go", []byte(textGo), 0o644); err != nil {
return err
}
return nil
}
func writeDburlLicense(dir string, licenseStart int, licenseAuthor string) error {
s := fmt.Sprintf(license, licenseStart, time.Now().Year(), licenseAuthor)
if err := os.WriteFile(filepath.Join(dir, "LICENSE"), append([]byte(s), '\n'), 0o644); err != nil {
return err
}
return nil
}
func writeCommands(name string) error {
// format and write internal.go
var names, descs string
for _, s := range sections {
names += fmt.Sprintf("%q,\n", s)
descs += fmt.Sprintf("// %s\n\t{\n", s)
for _, desc := range cmds[s] {
descs += fmt.Sprintf("\t%s,\n", desc)
}
descs += fmt.Sprint("},\n")
}
buf, err := format.Source([]byte(fmt.Sprintf(descsTpl, names, descs)))
if err != nil {
return err
}
return os.WriteFile(name, buf, 0644)
}
var (
// baseDrivers are drivers included in a build with no build tags listed.
baseDrivers = map[string]DriverInfo{}
// mostDrivers are drivers included with the most tag. Populated below.
mostDrivers = map[string]DriverInfo{}
// allDrivers are drivers forced to 'all' build tag.
allDrivers = map[string]DriverInfo{}
// badDrivers are drivers forced to 'bad' build tag.
badDrivers = map[string]DriverInfo{}
// wireDrivers are the wire compatible drivers.
wireDrivers = map[string]DriverInfo{}
)
// cmds are the meta command descriptions.
var cmds map[string][]desc
type DriverInfo struct {
// Tag is the build Tag / name of the directory the driver lives in.
Tag string
// Driver is the Go SQL Driver Driver (parsed from the import tagged with //
// DRIVER: ), otherwise same as the tag / directory Driver.
Driver string
// Pkg is the imported driver package, taken from the import tagged with
// DRIVER.
Pkg string
// Desc is the descriptive text of the driver, parsed from doc comment, ie,
// "Package defines and registers usql's ."
Desc string
// URL is the driver's reference URL, parsed from doc comment's "See: ".
URL string
// CGO is whether or not the driver requires CGO, based on presence of
// 'Requires CGO.' in the comment
CGO bool
// Aliases are the parsed Alias: entries.
Aliases [][]string
// Wire indicates it is a Wire compatible driver.
Wire bool
// Group is the build Group
Group string
}
func parseDriverInfo(tag, filename string) (DriverInfo, error) {
f, err := parser.ParseFile(token.NewFileSet(), filename, nil, parser.ParseComments)
if err != nil {
return DriverInfo{}, err
}
name := tag
var pkg string
for _, imp := range f.Imports {
if imp.Comment == nil || len(imp.Comment.List) == 0 || !strings.Contains(imp.Comment.List[0].Text, "DRIVER") {
continue
}
pkg = imp.Path.Value[1 : len(imp.Path.Value)-1]
if i := strings.Index(imp.Comment.List[0].Text, ":"); i != -1 {
name = strings.TrimSpace(imp.Comment.List[0].Text[i+1:])
}
break
}
// parse doc comment
comment := f.Doc.Text()
prefix := "Package " + tag + " defines and registers usql's "
if !strings.HasPrefix(comment, prefix) {
return DriverInfo{}, fmt.Errorf("invalid doc comment prefix for driver %q", tag)
}
desc := strings.TrimPrefix(comment, prefix)
i := strings.Index(desc, " driver.")
if i == -1 {
return DriverInfo{}, fmt.Errorf("cannot find description suffix for driver %q", tag)
}
desc = strings.TrimSpace(desc[:i])
if desc == "" {
return DriverInfo{}, fmt.Errorf("unable to parse description for driver %q", tag)
}
// parse alias:
var aliases [][]string
aliasesm := aliasRE.FindAllStringSubmatch(comment, -1)
for _, m := range aliasesm {
s := strings.Split(m[1], ",")
aliases = append(aliases, []string{
strings.TrimSpace(s[0]),
strings.TrimSpace(s[1]),
})
}
// parse see: url
urlm := seeRE.FindAllStringSubmatch(comment, -1)
if urlm == nil {
return DriverInfo{}, fmt.Errorf("missing See: for driver %q", tag)
}
// parse group:
group := "most"
if groupm := groupRE.FindAllStringSubmatch(comment, -1); groupm != nil {
group = strings.TrimSpace(groupm[0][1])
}
return DriverInfo{
Tag: tag,
Driver: name,
Pkg: pkg,
Desc: cleanRE.ReplaceAllString(desc, ""),
URL: strings.TrimSpace(urlm[0][1]),
CGO: strings.Contains(cleanRE.ReplaceAllString(comment, ""), "Requires CGO."),
Aliases: aliases,
Group: group,
}, nil
}
// desc is a meta command description.
type desc struct {
Func string
Name string
Params string
Desc string
Hidden bool
Deprecated bool
}
func newDesc(funcName, alias string, v []string) desc {
name, params, descstr, hidden := v[0], "", "", false
switch n := len(v); {
case n == 1:
if i := strings.Index(name, ":"); i != -1 {
name, alias = name[:i], name[i+1:]
}
case n == 2:
descstr = v[1]
case n >= 3:
params, descstr = v[1], v[2]
}
if descstr == "" {
hidden, descstr = true, `alias for \`+alias
}
return desc{
Func: funcName,
Name: name,
Params: params,
Desc: descstr,
Hidden: hidden,
Deprecated: v[len(v)-1] == "DEPRECATED",
}
}
func (d desc) String() string {
s := strings.ReplaceAll(d.Desc, "{{CommandName}}", "` + text.CommandName + `")
return fmt.Sprintf("{%s, `%s`, `%s`, `%s`, %t, %t}", d.Func, d.Name, d.Params, s, d.Hidden, d.Deprecated)
}
func findCommand(name string) string {
for _, s := range sections {
for _, d := range cmds[s] {
if d.Func == name {
return d.Name
}
}
}
panic(fmt.Sprintf("unable to find command for %s", name))
}
func buildDriverTable(includeTagSummary bool) string {
hdr := []string{"Database", "Scheme / Tag", "Scheme Aliases", "Driver Package / Notes"}
widths := []int{len(hdr[0]), len(hdr[1]), len(hdr[2]), len(hdr[3])}
baseRows, widths := buildRows(baseDrivers, widths)
mostRows, widths := buildRows(mostDrivers, widths)
allRows, widths := buildRows(allDrivers, widths)
badRows, widths := buildRows(badDrivers, widths)
wireRows, widths := buildRows(wireDrivers, widths)
s := tableRows(widths, ' ', hdr)
s += tableRows(widths, '-')
s += tableRows(widths, ' ', baseRows...)
s += tableRows(widths, ' ')
s += tableRows(widths, ' ', mostRows...)
s += tableRows(widths, ' ')
s += tableRows(widths, ' ', allRows...)
s += tableRows(widths, ' ')
s += tableRows(widths, ' ', wireRows...)
s += tableRows(widths, ' ')
s += tableRows(widths, ' ', badRows...)
if includeTagSummary {
s += tableRows(widths, ' ')
s += tableRows(widths, ' ',
[]string{"**NO DRIVERS**", "`no_base`", "", "_no base drivers (useful for development)_"},
[]string{"**MOST DRIVERS**", "`most`", "", "_all stable drivers_"},
[]string{"**ALL DRIVERS**", "`all`", "", "_all drivers, excluding bad drivers_"},
[]string{"**BAD DRIVERS**", "`bad`", "", "_bad drivers (broken/non-working drivers)_"},
[]string{"**NO <TAG>**", "`no_`", "", "_exclude driver with ``_"},
)
}
return s + "\n" + buildTableLinks(baseDrivers, mostDrivers, allDrivers, badDrivers)
}
func buildRows(m map[string]DriverInfo, widths []int) ([][]string, []int) {
var drivers []DriverInfo
for _, v := range m {
drivers = append(drivers, v)
}
sort.Slice(drivers, func(i, j int) bool {
switch {
case drivers[i].Group == "base":
return baseOrder[drivers[i].Driver] < baseOrder[drivers[j].Driver]
}
return strings.ToLower(drivers[i].Desc) < strings.ToLower(drivers[j].Desc)
})
var rows [][]string
for i, v := range drivers {
notes := ""
if v.CGO {
notes += " [†][f-cgo]"
}
if v.Wire {
notes += " [‡][f-wire]"
}
rows = append(rows, []string{
v.Desc,
"`" + v.Tag + "`",
buildAliases(v),
fmt.Sprintf("[%s][d-%s]%s", v.Pkg, v.Tag, notes),
})
// calc max
for j := 0; j < len(rows[i]); j++ {
widths[j] = max(runewidth.StringWidth(rows[i][j]), widths[j])
}
}
return rows, widths
}
func buildAliases(v DriverInfo) string {
name := v.Tag
if v.Wire {
name = v.Driver
}
_, aliases := dburl.SchemeDriverAndAliases(name)
if v.Wire {
aliases = append(aliases, name)
}
for i := 0; i < len(aliases); i++ {
if !v.Wire && aliases[i] == v.Tag {
aliases[i] = v.Driver
}
}
fileTypes := dburl.FileTypes()
if slices.Contains(fileTypes, name) {
aliases = append(aliases, `file`)
}
if len(aliases) > 0 {
return "`" + strings.Join(aliases, "`, `") + "`"
}
return ""
}
func tableRows(widths []int, c rune, rows ...[]string) string {
padding := string(c)
if len(rows) == 0 {
v := make([]string, len(widths))
if c == '-' {
for i, w := range widths {
v[i] = strings.Repeat(padding, w)
}
padding = " "
}
rows = [][]string{v}
}
var s string
for _, row := range rows {
for i := 0; i < len(row); i++ {
s += "|" + padding + row[i] + strings.Repeat(padding, widths[i]-runewidth.StringWidth(row[i])) + padding
}
s += "|\n"
}
return s
}
func buildTableLinks(drivers ...map[string]DriverInfo) string {
var d []DriverInfo
for _, m := range drivers {
for _, v := range m {
d = append(d, v)
}
}
sort.Slice(d, func(i, j int) bool {
return d[i].Tag < d[j].Tag
})
var s string
for _, v := range d {
s += fmt.Sprintf("[d-%s]: %s\n", v.Tag, v.URL)
}
return s
}
func getDburlDir() string {
dir := filepath.Join(os.Getenv("GOPATH"), "src/github.com/xo/dburl")
var err error
if dir, err = realpath.Realpath(dir); err != nil {
panic(err)
}
return dir
}
// decodeCommand decodes a command.
func decodeCommand(d ast.Decl) (string, []desc, bool, error) {
f, ok := d.(*ast.FuncDecl)
if !ok || !isCommand(f) {
return "", nil, false, nil
}
switch section, descs, err := decodeCommandDoc(f); {
case err != nil:
return "", nil, false, err
case !slices.Contains(sections, section):
return "", nil, false, fmt.Errorf("meta command %s has invalid section name %q", f.Name, section)
case len(descs) == 0:
return "", nil, false, fmt.Errorf("meta command %s has no valid command descriptions", f.Name)
default:
return section, descs, true, nil
}
}
// isCommand returns true if a meta command matches the signature for a command
// func.
func isCommand(f *ast.FuncDecl) bool {
switch {
case !unicode.IsUpper(rune(f.Name.String()[0])),
f.Type.Params.NumFields() != 1,
f.Type.Results.NumFields() != 1,
fmt.Sprint(f.Type.Results.List[0].Type) != "error":
return false
}
if e, ok := f.Type.Params.List[0].Type.(*ast.StarExpr); !ok || fmt.Sprint(e.X) != "Params" {
return false
}
return true
}
// decodeCommandDoc decodes a meta command's doc comment.
func decodeCommandDoc(f *ast.FuncDecl) (string, []desc, error) {
name, doc := f.Name.String()+" is a ", f.Doc.Text()
if !strings.HasPrefix(doc, name) {
return "", nil, fmt.Errorf("meta command %s doc comment does not start with %q", f.Name, name)
}
i := strings.Index(doc, "meta command (")
if i == -1 {
return "", nil, fmt.Errorf("meta command %s doc comment missing %q", f.Name, "meta command (")
}
section := strings.TrimSpace(doc[len(name):i])
if i = strings.Index(doc, "Descs:\n\n"); i == -1 {
return "", nil, fmt.Errorf("meta command %s doc comment missing %q", f.Name, "Descs:")
}
descs, err := decodeCommandDescs(f.Name.String(), doc[i+len("Descs:\n\n"):])
if err != nil {
return "", nil, fmt.Errorf("meta command %s has invalid desc: %v", f.Name, err)
}
return section, descs, nil
}
// decodeCommandDescs
func decodeCommandDescs(funcName string, doc string) ([]desc, error) {
s := bufio.NewScanner(strings.NewReader(doc))
var descs []desc
var alias string
for i := 0; s.Scan(); i++ {
line := s.Text()
if !strings.HasPrefix(line, "\t") {
return nil, fmt.Errorf("line %d does not start with \\t", i+1)
}
v := strings.Split(line[1:], "\t")
switch {
case len(v) == 0:
return nil, fmt.Errorf("line %d is invalid", i+1)
case len(v[0]) == 0:
return nil, fmt.Errorf("line %d has invalid name", i+1)
}
descs = append(descs, newDesc(funcName, alias, v))
if alias == "" {
alias = descs[0].Name
}
}
return descs, nil
}
var baseOrder = map[string]int{
"postgres": 0,
"mysql": 1,
"sqlserver": 2,
"oracle": 3,
"sqlite3": 4,
"clickhouse": 5,
"csvq": 6,
}
// sections are the section names for meta commands.
var sections = []string{
"General",
"Help",
"Connection",
"Query Execute",
"Query View",
"Query Buffer",
"Informational",
"Variables",
"Input/Output",
"Control/Conditional",
"Transaction",
"Operating System/Environment",
}
// regexps.
var (
aliasRE = regexp.MustCompile(`(?m)^Alias:\s+(.*)$`)
seeRE = regexp.MustCompile(`(?m)^See:\s+(.*)$`)
groupRE = regexp.MustCompile(`(?m)^Group:\s+(.*)$`)
cleanRE = regexp.MustCompile(`[\r\n]`)
dirRE = regexp.MustCompile(`^([^/]+)/([^\./]+)\.go$`)
)
const (
driverTableStart = ""
driverTableEnd = ""
)
const descsTpl = `package metacmd
// Code generated by gen.go. DO NOT EDIT.
import (
"github.com/xo/usql/text"
)
// sections are the command description sections.
var sections = []string{
%s
}
// descs are the command descriptions.
var descs [][]desc
// cmds are the command lookup map.
var cmds map[string]func(*Params) error
func init() {
descs = [][]desc{
%s
}
cmds = make(map[string]func(*Params) error)
for i := range sections {
for _, desc := range descs[i] {
for _, n := range desc.Names() {
cmds[n] = desc.Func
}
}
}
}
`
const internalTagGo = `//go:build %s
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ %q // %s driver
)`
const internalGo = `// Package internal provides a way to obtain information about which database
// drivers were included at build.
package internal
// Code generated by gen.go. DO NOT EDIT.
// KnownBuildTags returns a map of known driver names to its respective build
// tags.
func KnownBuildTags() map[string]string{
return map[string]string{%s
}
}`
const licenseTextGo = `package text
// Code generated by gen.go. DO NOT EDIT.
// License contains the license text for usql.
const License = ` + "`%s`" + `
`
const license = `The MIT License (MIT)
Copyright (c) %d-%d %s
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.`
================================================
FILE: go.mod
================================================
module github.com/xo/usql
go 1.26
require (
github.com/ClickHouse/clickhouse-go/v2 v2.43.0
github.com/IBM/nzgo/v12 v12.0.10
github.com/MichaelS11/go-cql-driver v0.1.1
github.com/SAP/go-hdb v1.15.1
github.com/VoltDB/voltdb-client-go v1.0.18
github.com/alecthomas/chroma/v2 v2.23.1
github.com/alexbrainman/odbc v0.0.0-20250601004241-49e6b2bc0cf0
github.com/aliyun/aliyun-tablestore-go-sql-driver v0.0.0-20220418015234-4d337cb3eed9
github.com/amsokol/ignite-go-client v0.12.2
github.com/apache/arrow/go/v17 v17.0.0
github.com/apache/calcite-avatica-go/v5 v5.4.0
github.com/btnguyen2k/gocosmos v1.1.0
github.com/btnguyen2k/godynamo v1.3.0
github.com/chaisql/chai v0.18.0
github.com/couchbase/go_n1ql v0.0.0-20220303011133-0ed4bf93e31d
github.com/databricks/databricks-sql-go v1.10.0
github.com/datafuselabs/databend-go v0.9.1
github.com/docker/docker v28.5.2+incompatible
github.com/duckdb/duckdb-go/v2 v2.5.5
github.com/exasol/exasol-driver-go v1.0.16
github.com/go-git/go-billy/v5 v5.8.0
github.com/go-sql-driver/mysql v1.9.3
github.com/gocql/gocql v1.7.0
github.com/godror/godror v0.50.0
github.com/gohxs/readline v0.0.0-20171011095936-a780388e6e7c
github.com/google/go-cmp v0.7.0
github.com/google/goexpect v0.0.0-20210430020637-ab937bf7fd6f
github.com/googleapis/go-sql-spanner v1.24.0
github.com/jackc/pgx/v5 v5.8.0
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade
github.com/jmrobles/h2go v0.5.0
github.com/kenshaw/colors v0.2.3
github.com/kenshaw/rasterm v0.1.16
github.com/lib/pq v1.11.2
github.com/mattn/go-adodb v0.0.1
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-runewidth v0.0.19
github.com/mattn/go-sqlite3 v1.14.34
github.com/microsoft/go-mssqldb v1.9.8
github.com/mithrandie/csvq v1.18.1
github.com/mithrandie/csvq-driver v1.7.0
github.com/nakagami/firebirdsql v0.9.15
github.com/ory/dockertest/v3 v3.12.0
github.com/prestodb/presto-go-client v0.0.0-20240426182841-905ac40a1783
github.com/proullon/ramsql v0.1.4
github.com/sclgo/impala-go v1.4.1
github.com/sijms/go-ora/v2 v2.9.0
github.com/snowflakedb/gosnowflake v1.19.0
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.21.0
github.com/thda/tds v0.1.7
github.com/trinodb/trino-go-client v0.333.0
github.com/uber/athenadriver v1.1.15
github.com/vertica/vertica-sql-go v1.3.5
github.com/xo/dburl v0.24.2
github.com/xo/echartsgoja v0.1.1
github.com/xo/resvg v0.6.0
github.com/xo/tblfmt v0.16.0
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e
github.com/ydb-platform/ydb-go-sdk/v3 v3.127.2
github.com/yookoala/realpath v1.0.0
github.com/ziutek/mymysql v1.5.4
gorm.io/driver/bigquery v1.2.0
modernc.org/ql v1.4.31
modernc.org/sqlite v1.46.1
sqlflow.org/gohive v0.0.0-20240730014249-8960223660e2
sqlflow.org/gomaxcompute v0.0.0-20210805062559-c14ae028b44c
)
require (
cel.dev/expr v0.25.1 // indirect
cloud.google.com/go v0.123.0 // indirect
cloud.google.com/go/auth v0.18.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/bigquery v1.73.1 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/iam v1.5.3 // indirect
cloud.google.com/go/longrunning v0.8.0 // indirect
cloud.google.com/go/monitoring v1.24.3 // indirect
cloud.google.com/go/spanner v1.88.0 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/ClickHouse/ch-go v0.71.0 // indirect
github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.6.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/RaduBerinde/axisds v0.0.0-20250419182453-5135a0650657 // indirect
github.com/RaduBerinde/btreemap v0.0.0-20250419232817-bf0d809ae648 // indirect
github.com/VictoriaMetrics/easyproto v1.2.0 // indirect
github.com/aliyun/aliyun-tablestore-go-sdk v1.8.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/apache/arrow-go/v18 v18.5.1 // indirect
github.com/apache/arrow/go/v12 v12.0.1 // indirect
github.com/apache/arrow/go/v15 v15.0.2 // indirect
github.com/apache/thrift v0.22.0 // indirect
github.com/avast/retry-go v3.0.0+incompatible // indirect
github.com/aws/aws-sdk-go v1.55.8 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.10 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.10 // indirect
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.29 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 // indirect
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.5 // indirect
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.32.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.16 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect
github.com/aws/smithy-go v1.24.2 // indirect
github.com/beltran/gohive v1.8.1 // indirect
github.com/beltran/gosasl v1.0.0 // indirect
github.com/beltran/gssapi v0.0.0-20200324152954-d86554db4bab // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bitfield/gotestdox v0.2.2 // indirect
github.com/btnguyen2k/consu/checksum v1.1.1 // indirect
github.com/btnguyen2k/consu/g18 v0.1.0 // indirect
github.com/btnguyen2k/consu/gjrc v0.2.2 // indirect
github.com/btnguyen2k/consu/olaf v0.1.3 // indirect
github.com/btnguyen2k/consu/reddo v0.1.9 // indirect
github.com/btnguyen2k/consu/semita v0.1.5 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect
github.com/cockroachdb/crlib v0.0.0-20251122031428-fe658a2dbda1 // indirect
github.com/cockroachdb/errors v1.12.0 // indirect
github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect
github.com/cockroachdb/pebble/v2 v2.1.2 // indirect
github.com/cockroachdb/redact v1.1.6 // indirect
github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/coreos/go-oidc/v3 v3.17.0 // indirect
github.com/couchbase/go-couchbase v0.1.1 // indirect
github.com/couchbase/gomemcached v0.3.3 // indirect
github.com/couchbase/goutils v0.2.0 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/danieljoos/wincred v1.2.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/dnephin/pflag v1.0.7 // indirect
github.com/docker/cli v28.4.0+incompatible // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dop251/goja v0.0.0-20251201205617-2bb4c724c0f9 // indirect
github.com/dop251/goja_nodejs v0.0.0-20251015164255-5e94316bedaf // indirect
github.com/dromara/carbon/v2 v2.6.15 // indirect
github.com/duckdb/duckdb-go-bindings v0.3.4 // indirect
github.com/duckdb/duckdb-go-bindings/lib/darwin-amd64 v0.3.3 // indirect
github.com/duckdb/duckdb-go-bindings/lib/darwin-arm64 v0.3.3 // indirect
github.com/duckdb/duckdb-go-bindings/lib/linux-amd64 v0.3.3 // indirect
github.com/duckdb/duckdb-go-bindings/lib/linux-arm64 v0.3.3 // indirect
github.com/duckdb/duckdb-go-bindings/lib/windows-amd64 v0.3.3 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dvsekhvalnov/jose2go v1.8.0 // indirect
github.com/edsrzf/mmap-go v1.2.0 // indirect
github.com/elastic/go-sysinfo v1.15.4 // indirect
github.com/elastic/go-windows v1.0.2 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
github.com/exasol/error-reporting-go v0.2.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/getsentry/sentry-go v0.40.0 // indirect
github.com/go-faster/city v1.0.1 // indirect
github.com/go-faster/errors v0.7.1 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/go-zookeeper/zk v1.0.4 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/godror/knownpb v0.3.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/flatbuffers/go v0.0.0-20230110200425-62e4d2e5b215 // indirect
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 // indirect
github.com/google/pprof v0.0.0-20251213031049-b05bdaca462f // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/icholy/digest v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/jedib0t/go-pretty/v6 v6.7.7 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/klauspost/asmfmt v1.3.2 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-sixel v0.0.8 // indirect
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect
github.com/minio/minlz v1.0.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mithrandie/go-file/v2 v2.1.0 // indirect
github.com/mithrandie/go-text v1.6.0 // indirect
github.com/mithrandie/ternary v1.1.1 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.1.0 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/murfffi/gorich v0.3.0 // indirect
github.com/nakagami/chacha20 v0.1.0 // indirect
github.com/nathan-fiscaletti/consolesize-go v0.0.0-20220204101620-317176b6684d // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runc v1.3.1 // indirect
github.com/paulmach/orb v0.12.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.25 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.4 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/samber/lo v1.52.0 // indirect
github.com/segmentio/asm v1.2.1 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/soniakeys/quant v1.0.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/uber-go/tally v3.3.17+incompatible // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 // indirect
github.com/ydb-platform/ydb-go-genproto v0.0.0-20260128080146-c4ed16b24b37 // indirect
github.com/zeebo/xxh3 v1.1.0 // indirect
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.41.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.66.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0 // indirect
go.opentelemetry.io/otel v1.41.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.41.0 // indirect
go.opentelemetry.io/otel/sdk v1.41.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.41.0 // indirect
go.opentelemetry.io/otel/trace v1.41.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/telemetry v0.0.0-20260213145524-e0ab670178e1 // indirect
golang.org/x/term v0.40.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.42.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/api v0.269.0 // indirect
google.golang.org/genproto v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
gopkg.in/jcmturner/gokrb5.v6 v6.1.1 // indirect
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/gotestsum v1.13.0 // indirect
howett.net/plist v1.0.1 // indirect
modernc.org/b v1.1.0 // indirect
modernc.org/db v1.0.29 // indirect
modernc.org/file v1.0.19 // indirect
modernc.org/fileutil v1.4.0 // indirect
modernc.org/golex v1.1.0 // indirect
modernc.org/internal v1.1.9 // indirect
modernc.org/libc v1.69.0 // indirect
modernc.org/lldb v1.0.8 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sortutil v1.2.1 // indirect
modernc.org/strutil v1.2.1 // indirect
modernc.org/zappy v1.1.0 // indirect
)
================================================
FILE: go.sum
================================================
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU=
cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw=
cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY=
cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI=
cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4=
cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4=
cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0=
cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ=
cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk=
cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o=
cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s=
cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY=
cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw=
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=
cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA=
cloud.google.com/go/bigquery v1.73.1 h1:v//GZwdhtmCbZ87rOnxz7pectOGFS1GNRvrGTvLzka4=
cloud.google.com/go/bigquery v1.73.1/go.mod h1:KSLx1mKP/yGiA8U+ohSrqZM1WknUnjZAxHAQZ51/b1k=
cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY=
cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s=
cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM=
cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI=
cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY=
cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI=
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I=
cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4=
cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0=
cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs=
cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc=
cloud.google.com/go/datacatalog v1.26.1 h1:bCRKA8uSQN8wGW3Tw0gwko4E9a64GRmbW1nCblhgC2k=
cloud.google.com/go/datacatalog v1.26.1/go.mod h1:2Qcq8vsHNxMDgjgadRFmFG47Y+uuIVsyEGUrlrKEdrg=
cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM=
cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ=
cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo=
cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE=
cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I=
cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ=
cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo=
cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo=
cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ=
cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4=
cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0=
cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU=
cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU=
cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y=
cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg=
cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk=
cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk=
cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg=
cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM=
cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA=
cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o=
cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A=
cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0=
cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0=
cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc=
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc=
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=
cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=
cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8=
cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08=
cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8=
cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk=
cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4=
cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w=
cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE=
cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM=
cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY=
cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s=
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA=
cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o=
cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ=
cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU=
cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY=
cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34=
cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs=
cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg=
cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E=
cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU=
cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0=
cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA=
cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0=
cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4=
cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o=
cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk=
cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg=
cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4=
cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg=
cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c=
cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y=
cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A=
cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4=
cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY=
cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s=
cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI=
cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA=
cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4=
cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0=
cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU=
cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU=
cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc=
cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs=
cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg=
cloud.google.com/go/spanner v1.88.0 h1:HS+5TuEYZOVOXj9K+0EtrbTw7bKBLrMe3vgGsbnehmU=
cloud.google.com/go/spanner v1.88.0/go.mod h1:MzulBwuuYwQUVdkZXBBFapmXee3N+sQrj2T/yup6uEE=
cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=
cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc=
cloud.google.com/go/storage v1.59.0 h1:9p3yDzEN9Vet4JnbN90FECIw6n4FCXcKBK1scxtQnw8=
cloud.google.com/go/storage v1.59.0/go.mod h1:cMWbtM+anpC74gn6qjLh+exqYcfmB9Hqe5z6adx+CLI=
cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw=
cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g=
cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU=
cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4=
cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0=
cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo=
cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo=
cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE=
cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg=
cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=
cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1/go.mod h1:Ng3urmn6dYe8gnbCMoHHVl5APYz2txho3koEkV2o2HA=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 h1:jWQK1GI+LeGGUKBADtcH2rRqPxYB1Ljwms5gFA2LqrM=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4/go.mod h1:8mwH4klAm9DUgR2EEHyEEAQlRDvLPyg5fQry3y+cDew=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ClickHouse/ch-go v0.71.0 h1:bUdZ/EZj/LcVHsMqaRUP2holqygrPWQKeMjc6nZoyRM=
github.com/ClickHouse/ch-go v0.71.0/go.mod h1:NwbNc+7jaqfY58dmdDUbG4Jl22vThgx1cYjBw0vtgXw=
github.com/ClickHouse/clickhouse-go/v2 v2.43.0 h1:fUR05TrF1GyvLDa/mAQjkx7KbgwdLRffs2n9O3WobtE=
github.com/ClickHouse/clickhouse-go/v2 v2.43.0/go.mod h1:o6jf7JM/zveWC/PP277BLxjHy5KjnGX/jfljhM4s34g=
github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.6.0 h1:BzsL0qE7LvtTEtXG7Dt5NS1EP0CQwI21HZfj9aGghhw=
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.6.0/go.mod h1:I7kE2kM3qCr9QPT4cU4cCFYkEpVyVr16YOGUHzy+nR0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
github.com/IBM/nzgo/v12 v12.0.10 h1:Mfc+lU/KyvNGMtprQNNGuGBgbrrlvCy0o8EUsm7fiH0=
github.com/IBM/nzgo/v12 v12.0.10/go.mod h1:4pvfEkfsrAdqlljsp8HNwv/uzNKy2fzoXBB1aRIssJg=
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c h1:RGWPOewvKIROun94nF7v2cua9qP+thov/7M50KEoeSU=
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/MichaelS11/go-cql-driver v0.1.1 h1:ntFKov/39Tl36HckP4tzld3XMeyDYHHO00MiZNdoL1A=
github.com/MichaelS11/go-cql-driver v0.1.1/go.mod h1:rMwGk5bMWiYI/If6r6dbqEfZG6nQLvqJHTplv5yTDaw=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/RaduBerinde/axisds v0.0.0-20250419182453-5135a0650657 h1:8XBWWQD+vFF+JqOsm16t0Kab1a7YWV8+GISVEP8AuZ8=
github.com/RaduBerinde/axisds v0.0.0-20250419182453-5135a0650657/go.mod h1:UHGJonU9z4YYGKJxSaC6/TNcLOBptpmM5m2Cksbnw0Y=
github.com/RaduBerinde/btreemap v0.0.0-20250419232817-bf0d809ae648 h1:0s1dtMVp3XcQ1tHazU9OCLCKoqj4TRD8GFU5SscItMM=
github.com/RaduBerinde/btreemap v0.0.0-20250419232817-bf0d809ae648/go.mod h1:0tr7FllbE9gJkHq7CVeeDDFAFKQVy5RnCSSNBOvdqbc=
github.com/SAP/go-hdb v1.15.1 h1:CZP/e14rlZgw6Dtnon+be4VR5NEBW3ygrCSLAnDVce4=
github.com/SAP/go-hdb v1.15.1/go.mod h1:hudYRn/mEuyBvt7ekr4qmRbGpcJo++D/9B9vLHrjtZY=
github.com/UNO-SOFT/zlog v0.8.1 h1:TEFkGJHtUfTRgMkLZiAjLSHALjwSBdw6/zByMC5GJt4=
github.com/UNO-SOFT/zlog v0.8.1/go.mod h1:yqFOjn3OhvJ4j7ArJqQNA+9V+u6t9zSAyIZdWdMweWc=
github.com/VictoriaMetrics/easyproto v1.2.0 h1:FJT9uNXA2isppFuJErbLqD306KoFlehl7Wn2dg/6oIE=
github.com/VictoriaMetrics/easyproto v1.2.0/go.mod h1:QlGlzaJnDfFd8Lk6Ci/fuLxfTo3/GThPs2KH23mv710=
github.com/VoltDB/voltdb-client-go v1.0.18 h1:iVUw3z2MdV4oc6J45+W17Hn9TVrY5ArYN2ldxyqqxhs=
github.com/VoltDB/voltdb-client-go v1.0.18/go.mod h1:2wVijB3aloOvpaygTJdgundq0hb3/sCPidzFClcmfXo=
github.com/aclements/go-perfevent v0.0.0-20240301234650-f7843625020f h1:JjxwchlOepwsUWcQwD2mLUAGE9aCp0/ehy6yCHFBOvo=
github.com/aclements/go-perfevent v0.0.0-20240301234650-f7843625020f/go.mod h1:tMDTce/yLLN/SK8gMOxQfnyeMeCg8KGzp0D1cbECEeo=
github.com/ahmetb/dlog v0.0.0-20170105205344-4fb5f8204f26 h1:3YVZUqkoev4mL+aCwVOSWV4M7pN+NURHL38Z2zq5JKA=
github.com/ahmetb/dlog v0.0.0-20170105205344-4fb5f8204f26/go.mod h1:ymXt5bw5uSNu4jveerFxE0vNYxF8ncqbptntMaFMg3k=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alexbrainman/odbc v0.0.0-20250601004241-49e6b2bc0cf0 h1:gUrYWktqvF8PVb2SIBQR5WsFxjctn7d1JBIx/FrSzik=
github.com/alexbrainman/odbc v0.0.0-20250601004241-49e6b2bc0cf0/go.mod h1:c5eyz5amZqTKvY3ipqerFO/74a/8CYmXOahSr40c+Ww=
github.com/aliyun/aliyun-tablestore-go-sdk v1.7.3/go.mod h1:PWqq46gZJf7mnYTAuTmxKgx6EwJu3oBpOs1s2V0EZPM=
github.com/aliyun/aliyun-tablestore-go-sdk v1.8.0 h1:qmBTupWC9eFbnN64UiXs1Zgba2wnRe/fm4NfIMunmU0=
github.com/aliyun/aliyun-tablestore-go-sdk v1.8.0/go.mod h1:JzOJMpBPGN+4cuYnrGO5wdwphEyqbeGVY2vCaiAcNW8=
github.com/aliyun/aliyun-tablestore-go-sql-driver v0.0.0-20220418015234-4d337cb3eed9 h1:DpsLZRlqHH1b2QyoLDK1/MtUtm7zuiQweA6hsTY97do=
github.com/aliyun/aliyun-tablestore-go-sql-driver v0.0.0-20220418015234-4d337cb3eed9/go.mod h1:4yTI9ZSYNi4eENMKL8VWP22MzoDKeqDT4j7Fd103BVQ=
github.com/amsokol/ignite-go-client v0.12.2 h1:q4Mr+UUiKVnR7ykjR1YARVS5jp+ZU6ekCIs0V4WgFDo=
github.com/amsokol/ignite-go-client v0.12.2/go.mod h1:K3tKJGcLQORFD+ds7f0f9fl88tv0KZcpfuNhzRyuLVE=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/arrow-go/v18 v18.5.1 h1:yaQ6zxMGgf9YCYw4/oaeOU3AULySDlAYDOcnr4LdHdI=
github.com/apache/arrow-go/v18 v18.5.1/go.mod h1:OCCJsmdq8AsRm8FkBSSmYTwL/s4zHW9CqxeBxEytkNE=
github.com/apache/arrow/go/v12 v12.0.1 h1:JsR2+hzYYjgSUkBSaahpqCetqZMr76djX80fF/DiJbg=
github.com/apache/arrow/go/v12 v12.0.1/go.mod h1:weuTY7JvTG/HDPtMQxEUp7pU73vkLWMLpY67QwZ/WWw=
github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE=
github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA=
github.com/apache/arrow/go/v17 v17.0.0 h1:RRR2bdqKcdbss9Gxy2NS/hK8i4LDMh23L6BbkN5+F54=
github.com/apache/arrow/go/v17 v17.0.0/go.mod h1:jR7QHkODl15PfYyjM2nU+yTLScZ/qfj7OSUZmJ8putc=
github.com/apache/calcite-avatica-go/v5 v5.4.0 h1:snCrhGlwDgqNA2Rp7RUABjNX2zX+EfLk5K7PSJRPD5w=
github.com/apache/calcite-avatica-go/v5 v5.4.0/go.mod h1:ed2DNx4xLzxrVYbvZU9Nv97LwyO6c0J7oGnOP4HbqZk=
github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc=
github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g=
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
github.com/aws/aws-sdk-go v1.37.32/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
github.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.29 h1:dQFhl5Bnl/SK1EVpgElK5dckAE+lMHXnl5WCeRvNEG0=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.29/go.mod h1:BtBP1TCx5BTCh1uTVXpo3b/odnRECBpZdL5oHQarJJs=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.4 h1:s8fbFscel8NLpnz+ggR7ncW+lqhXIkmyHbgbPeT8yyM=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.4/go.mod h1:BazuWe/q/mMJ/NrSJBTbNBJiLq6u8reodbEZ4giRms4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 h1:eZioDaZGJ0tMM4gzmkNIO2aAoQd+je7Ug7TkvAzlmkU=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18/go.mod h1:CCXwUKAJdoWr6/NcxZ+zsiPr6oH/Q5aTooRGYieAyj4=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.5 h1:mSBrQCXMjEvLHsYyJVbN8QQlcITXwHEuu+8mX9e2bSo=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.5/go.mod h1:eEuD0vTf9mIzsSjGBFWIaNQwtH5/mzViJOVQfnMY5DE=
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.32.9 h1:mB79k/ZTxQL4oDPxLAf2rhcUEvXlHkj3loGA2O9xREk=
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.32.9/go.mod h1:wXQmLDkBNh60jxAaRldON9poacv+GiSIBw/kRuT/mtE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10 h1:fJvQ5mIBVfKtiyx0AHY6HeWcRX5LGANLpq8SVR+Uazs=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10/go.mod h1:Kzm5e6OmNH8VMkgK9t+ry5jEih4Y8whqs+1hrkxim1I=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.16 h1:8g4OLy3zfNzLV20wXmZgx+QumI9WhWHnd4GCdvETxs4=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.16/go.mod h1:5a78jwLMs7BaesU0UIhLfVy2ZmOEgOy6ewYQXKTD37Q=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 h1:/A/xDuZAVD2BpsS2fftFRo/NoEKQJ8YTnJDEHBy2Gtg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18/go.mod h1:hWe9b4f+djUQGmyiGEeOnZv69dtMSgpDRIvNMvuvzvY=
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2 h1:M1A9AjcFwlxTLuf0Faj88L8Iqw0n/AJHjpZTQzMMsSc=
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2/go.mod h1:KsdTV6Q9WKUZm2mNJnUFmIoXfZux91M3sr/a4REX8e0=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/beltran/gohive v1.8.1 h1:qlygmroy3mKtKIQSpV/FqXJHty1LsPxF+JTQA5mbjwU=
github.com/beltran/gohive v1.8.1/go.mod h1:BCgNAhr/wnbyXfp2yN9ZY4pVrGrtVqG4hhNDDXIal1U=
github.com/beltran/gosasl v1.0.0 h1:iiRtLxkvKhrNv3Ohh/n2NiyyfwIo/UbMzy/dZWiUHXE=
github.com/beltran/gosasl v1.0.0/go.mod h1:Qx8cW6jkI8riyzmklj80kAIkv+iezFUTBiGU0qHhHes=
github.com/beltran/gssapi v0.0.0-20200324152954-d86554db4bab h1:ayfcn60tXOSYy5zUN1AMSTQo4nJCf7hrdzAVchpPst4=
github.com/beltran/gssapi v0.0.0-20200324152954-d86554db4bab/go.mod h1:GLe4UoSyvJ3cVG+DVtKen5eAiaD8mAJFuV5PT3Eeg9Q=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bitfield/gotestdox v0.2.2 h1:x6RcPAbBbErKLnapz1QeAlf3ospg8efBsedU93CDsnE=
github.com/bitfield/gotestdox v0.2.2/go.mod h1:D+gwtS0urjBrzguAkTM2wodsTQYFHdpx8eqRJ3N+9pY=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/btnguyen2k/consu/checksum v1.1.1 h1:kdIJGk3yl83Nn1HxZRk3bXJM0xvlwTcTYUmZ8BiloPU=
github.com/btnguyen2k/consu/checksum v1.1.1/go.mod h1:/zZ8EXdphDYEkBFua51hK9y3rODCPIkiZYnCDlHT670=
github.com/btnguyen2k/consu/g18 v0.1.0 h1:IoS5w5QlOfkcrNOHJyICD6PgqLh+J5fIDqy3vRBVcVM=
github.com/btnguyen2k/consu/g18 v0.1.0/go.mod h1:gTPcr87XdCLDISusRQyDey22/ZOw6bLh6EChxTLx6/c=
github.com/btnguyen2k/consu/gjrc v0.2.2 h1:CAY8xPgvtWc7EMTE9gxam/BxMgTRRpc4Hs9QEyYxRUc=
github.com/btnguyen2k/consu/gjrc v0.2.2/go.mod h1:Sc0NehbI0i8V6FAY9qX1we9XXbWNnrMOb9jNpYqGBWk=
github.com/btnguyen2k/consu/olaf v0.1.3 h1:0dWWmN5nOB/9pJdo7o1S3wR2+l3kG7pXHv3Vwki8uNM=
github.com/btnguyen2k/consu/olaf v0.1.3/go.mod h1:6ybEnJcdcK/PNiSfkKnMoxYuKyH2vJPBvHRuuZpPvD8=
github.com/btnguyen2k/consu/reddo v0.1.7/go.mod h1:pdY5oIVX3noZIaZu3nvoKZ59+seXL/taXNGWh9xJDbg=
github.com/btnguyen2k/consu/reddo v0.1.8/go.mod h1:pdY5oIVX3noZIaZu3nvoKZ59+seXL/taXNGWh9xJDbg=
github.com/btnguyen2k/consu/reddo v0.1.9 h1:NZyEzRcDXzksNMnvZVZyJmGN6ZQQmHg4hIPCPbfsCBE=
github.com/btnguyen2k/consu/reddo v0.1.9/go.mod h1:pdY5oIVX3noZIaZu3nvoKZ59+seXL/taXNGWh9xJDbg=
github.com/btnguyen2k/consu/semita v0.1.5 h1:fu71xNJTbCV8T+6QPJdJu3bxtmLWvTjCepkvujF74+I=
github.com/btnguyen2k/consu/semita v0.1.5/go.mod h1:fksCe3L4kxiJVnKKhUXKI8mcFdB9974mtedwUVVFu1M=
github.com/btnguyen2k/consu/semver v0.2.1 h1:le0FzrM7u0IOR4MnOyBySHpZ/p3vV4JjofAhPB7edWE=
github.com/btnguyen2k/consu/semver v0.2.1/go.mod h1:jxK/nwIWTXcWlcWcfkhPfLWq9b5dVzAtJLycySBFHTc=
github.com/btnguyen2k/gocosmos v1.1.0 h1:16OIhDTAK6ChyjQMjG+yHEO/MGdO8UsCeJ/2xiY9eRE=
github.com/btnguyen2k/gocosmos v1.1.0/go.mod h1:g599FZ7hAt6XZ108baotFrBr4U/r5xoyyQ8VyAZerL8=
github.com/btnguyen2k/godynamo v1.3.0 h1:8Ri9gVWMvBWlD5P04AEVrl2QcmMQR7KgC3zSCl/YLWw=
github.com/btnguyen2k/godynamo v1.3.0/go.mod h1:vNE48BoUAZS4F5ohrZ7suhw61DmCiXSClKrJfr2maTo=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chaisql/chai v0.18.0 h1:lfamFZCeg8MeYX9cUeoXyZSOmVXuB+5nzhvXjUx4IBc=
github.com/chaisql/chai v0.18.0/go.mod h1:9X+hiE9DUjCHIznbke4bUs2aWyWxMAfAHf7itlP5Fn0=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/cockroachdb/crlib v0.0.0-20251122031428-fe658a2dbda1 h1:iX0YCYC5Jbt2/g7zNTP/QxhrV8Syp5kkzNiERKeN1uE=
github.com/cockroachdb/crlib v0.0.0-20251122031428-fe658a2dbda1/go.mod h1:NjNuToN/FbhwH1cCyM9G4Rhtxx+ZaOgtoqFR+thng7w=
github.com/cockroachdb/datadriven v1.0.3-0.20250407164829-2945557346d5 h1:UycK/E0TkisVrQbSoxvU827FwgBBcZ95nRRmpj/12QI=
github.com/cockroachdb/datadriven v1.0.3-0.20250407164829-2945557346d5/go.mod h1:jsaKMvD3RBCATk1/jbUZM8C9idWBJME9+VRZ5+Liq1g=
github.com/cockroachdb/errors v1.12.0 h1:d7oCs6vuIMUQRVbi6jWWWEJZahLCfJpnJSVobd1/sUo=
github.com/cockroachdb/errors v1.12.0/go.mod h1:SvzfYNNBshAVbZ8wzNc/UPK3w1vf0dKDUP41ucAIf7g=
github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 h1:ASDL+UJcILMqgNeV5jiqR4j+sTuvQNHdf2chuKj1M5k=
github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506/go.mod h1:Mw7HqKr2kdtu6aYGn3tPmAftiP3QPX63LdK/zcariIo=
github.com/cockroachdb/metamorphic v0.0.0-20231108215700-4ba948b56895 h1:XANOgPYtvELQ/h4IrmPAohXqe2pWA8Bwhejr3VQoZsA=
github.com/cockroachdb/metamorphic v0.0.0-20231108215700-4ba948b56895/go.mod h1:aPd7gM9ov9M8v32Yy5NJrDyOcD8z642dqs+F0CeNXfA=
github.com/cockroachdb/pebble/v2 v2.1.2 h1:IwYt+Y2Cdw6egblwk1kWzdmJvD2680t5VK/3i0BJ6IA=
github.com/cockroachdb/pebble/v2 v2.1.2/go.mod h1:Aza05DCCc05ghIJZkB4Q/axv/JK9wx5cFwWcnhG0eGw=
github.com/cockroachdb/redact v1.1.6 h1:zXJBwDZ84xJNlHl1rMyCojqyIxv+7YUpQiJLQ7n4314=
github.com/cockroachdb/redact v1.1.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b h1:VXvSNzmr8hMj8XTuY0PT9Ane9qZGul/p67vGYwl9BFI=
github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b/go.mod h1:yBRu/cnL4ks9bgy4vAASdjIW+/xMlFwuHKqtmh3GZQg=
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb h1:3bCgBvB8PbJVMX1ouCcSIxvsqKPYM7gs72o0zC76n9g=
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/couchbase/go-couchbase v0.1.1 h1:ClFXELcKj/ojyoTYbsY34QUrrYCBi/1G749sXSCkdhk=
github.com/couchbase/go-couchbase v0.1.1/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
github.com/couchbase/go_n1ql v0.0.0-20220303011133-0ed4bf93e31d h1:jOxYt3U9z+tj2WDvacvBhXmHXDt+EUR5Hbu56wTw6QY=
github.com/couchbase/go_n1ql v0.0.0-20220303011133-0ed4bf93e31d/go.mod h1:Rn19fO9CVfhJkqyIED9ixL5Kh5XuH7hXgDTxyfGY7hM=
github.com/couchbase/gomemcached v0.3.3 h1:D7qqXLO8wNa4pn5oE65lT3pA3IeStn4joT7/JgGXzKc=
github.com/couchbase/gomemcached v0.3.3/go.mod h1:pISAjweI42vljCumsJIo7CVhqIMIIP9g3Wfhl1JJw68=
github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE=
github.com/couchbase/goutils v0.2.0 h1:2hTV1VUunDcgd2ctYZ27Zv5ShACS0NAZ86viOFwp1gU=
github.com/couchbase/goutils v0.2.0/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ=
github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs=
github.com/databricks/databricks-sql-go v1.10.0 h1:U17EKVC+hLP87swFMe2N6UUVektwUgTvT2pMDaDc46g=
github.com/databricks/databricks-sql-go v1.10.0/go.mod h1:qC010ucrtqrNXY2UOcoczbfPD4gJ1jr1y6TL7iqyxPk=
github.com/datafuselabs/databend-go v0.9.1 h1:hNQD+gaXPYC/raIrmtCpdW58l+MjGajGZmoGbKibEVc=
github.com/datafuselabs/databend-go v0.9.1/go.mod h1:G90oi9bpPFdwpBeRW1DNBYjLsEtzFVq8R5nZCPqNn1Q=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk=
github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE=
github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dop251/goja v0.0.0-20251201205617-2bb4c724c0f9 h1:3uSSOd6mVlwcX3k5OYOpiDqFgRmaE2dBfLvVIFWWHrw=
github.com/dop251/goja v0.0.0-20251201205617-2bb4c724c0f9/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
github.com/dop251/goja_nodejs v0.0.0-20251015164255-5e94316bedaf h1:gbmvliZnCut4NjaPSNOQlfqBoZ9C5Dpf72mHMMYhgVE=
github.com/dop251/goja_nodejs v0.0.0-20251015164255-5e94316bedaf/go.mod h1:Tb7Xxye4LX7cT3i8YLvmPMGCV92IOi4CDZvm/V8ylc0=
github.com/dromara/carbon/v2 v2.6.15 h1:3HuC3XcWczIHUTbg/f0CSVydtKEdM+P0GM1sdsbwXmI=
github.com/dromara/carbon/v2 v2.6.15/go.mod h1:NGo3reeV5vhWCYWcSqbJRZm46MEwyfYI5EJRdVFoLJo=
github.com/duckdb/duckdb-go-bindings v0.3.4 h1:K0h/G9AdQBAJ0cTI+Z2iDHT2/K94pBZXZawsG3qUAj8=
github.com/duckdb/duckdb-go-bindings v0.3.4/go.mod h1:zS7OpBP8zwVlP38OljRZOnqWYlNd4KLcVfMoA1JFzpk=
github.com/duckdb/duckdb-go-bindings/lib/darwin-amd64 v0.3.3 h1:ue8BtIOSt+2Bt2fEfTAvBcQLxzBFhgfCcyzPtqQWTRA=
github.com/duckdb/duckdb-go-bindings/lib/darwin-amd64 v0.3.3/go.mod h1:EnAvZh1kNJHp5yF+M1ZHNEvapnmt6anq1xXHVrAGqMo=
github.com/duckdb/duckdb-go-bindings/lib/darwin-arm64 v0.3.3 h1:2TrSeTgtwi3WIvub9ba0mny+AClSNo1w0Ghszc2B8lQ=
github.com/duckdb/duckdb-go-bindings/lib/darwin-arm64 v0.3.3/go.mod h1:IGLSeEcFhNeZF16aVjQCULD7TsFZKG5G7SyKJAXKp5c=
github.com/duckdb/duckdb-go-bindings/lib/linux-amd64 v0.3.3 h1:GN0cexhfE7uLb7qgDmsYG324wKF15nW+O7v5+NGalS4=
github.com/duckdb/duckdb-go-bindings/lib/linux-amd64 v0.3.3/go.mod h1:KAIynZ0GHCS7X5fRyuFnQMg/SZBPK/bS9OCOVojClxw=
github.com/duckdb/duckdb-go-bindings/lib/linux-arm64 v0.3.3 h1:bIJV+ct6yvMXjy+N3bfILFd0fkTK50AUhUTerkY40/8=
github.com/duckdb/duckdb-go-bindings/lib/linux-arm64 v0.3.3/go.mod h1:81SGOYoEUs8qaAfSk1wRfM5oobrIJ5KI7AzYhK6/bvQ=
github.com/duckdb/duckdb-go-bindings/lib/windows-amd64 v0.3.3 h1:SK2sunA/MPb2T3113iFzHv6DWeu+qrsw0DizTFrvM+Q=
github.com/duckdb/duckdb-go-bindings/lib/windows-amd64 v0.3.3/go.mod h1:K25pJL26ARblGDeuAkrdblFvUen92+CwksLtPEHRqqQ=
github.com/duckdb/duckdb-go/v2 v2.5.5 h1:TlK8ipnzoKW2aNrjGqRkFWLCDpJDxR/VwH8ezEcvVhw=
github.com/duckdb/duckdb-go/v2 v2.5.5/go.mod h1:6uIbC3gz36NCEygECzboygOo/Z9TeVwox/puG+ohWV0=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dvsekhvalnov/jose2go v1.8.0 h1:LqkkVKAlHFfH9LOEl5fe4p/zL02OhWE7pCufMBG2jLA=
github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/elastic/go-sysinfo v1.8.1/go.mod h1:JfllUnzoQV/JRYymbH3dO1yggI3mV2oTKSXsDHM+uIM=
github.com/elastic/go-sysinfo v1.15.4 h1:A3zQcunCxik14MgXu39cXFXcIw2sFXZ0zL886eyiv1Q=
github.com/elastic/go-sysinfo v1.15.4/go.mod h1:ZBVXmqS368dOn/jvijV/zHLfakWTYHBZPk3G244lHrU=
github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI=
github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
github.com/exasol/error-reporting-go v0.2.0 h1:nKIe4zYiTHbYrKJRlSNJcmGjTJCZredDh5akVHfIbRs=
github.com/exasol/error-reporting-go v0.2.0/go.mod h1:lUzRJqKLiSuYpqRUN2LVyj08WeHzhMEC/8Gmgtuqh1Y=
github.com/exasol/exasol-driver-go v1.0.16 h1:gN5hZccYSEQDOa04ktT3HAFYOl2CqddWLQYcGf6xpdk=
github.com/exasol/exasol-driver-go v1.0.16/go.mod h1:wFlvan1L7KGJ1eyaQzw/oT2pCPs6TEh0tBaUvHVciZM=
github.com/exasol/exasol-test-setup-abstraction-server/go-client v1.0.0 h1:+WyqR/tPCFD5sQSjoTokOOKigT9wu8CXwMWXST9xS+Y=
github.com/exasol/exasol-test-setup-abstraction-server/go-client v1.0.0/go.mod h1:XzrsDcGk0X2BkSUYKEOE9MdXdOwPdA9RyKRAPCPBHKs=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/getsentry/sentry-go v0.40.0 h1:VTJMN9zbTvqDqPwheRVLcp0qcUcM+8eFivvGocAaSbo=
github.com/getsentry/sentry-go v0.40.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s=
github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9 h1:r5GgOLGbza2wVHRzK7aAj6lWZjfbAwiu/RDCVOKjRyM=
github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gorp/gorp v2.2.0+incompatible h1:xAUh4QgEeqPPhK3vxZN+bzrim1z5Av6q837gtjUlshc=
github.com/go-gorp/gorp v2.2.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-zookeeper/zk v1.0.4 h1:DPzxraQx7OrPyXq2phlGlNSIyWEsAox0RJmjTseMV6I=
github.com/go-zookeeper/zk v1.0.4/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gocql/gocql v0.0.0-20200815110948-5378c8f664e9/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY=
github.com/gocql/gocql v1.7.0 h1:O+7U7/1gSN7QTEAaMEsJc1Oq2QHXvCWoF3DFK9HDHus=
github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFxZ+4=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godror/godror v0.50.0 h1:c0ZnGSDFT12E8HJfQwxtqcmybaIkbqACNk4lIfkkESc=
github.com/godror/godror v0.50.0/go.mod h1:kTMcxZzRw73RT5kn9v3JkBK4kHI6dqowHotqV72ebU8=
github.com/godror/knownpb v0.3.0 h1:+caUdy8hTtl7X05aPl3tdL540TvCcaQA6woZQroLZMw=
github.com/godror/knownpb v0.3.0/go.mod h1:PpTyfJwiOEAzQl7NtVCM8kdPCnp3uhxsZYIzZ5PV4zU=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/gohxs/readline v0.0.0-20171011095936-a780388e6e7c h1:yE35fKFwcelIte3q5q1/cPiY7pI7vvf5/j/0ddxNCKs=
github.com/gohxs/readline v0.0.0-20171011095936-a780388e6e7c/go.mod h1:9S/fKAutQ6wVHqm1jnp9D9sc5hu689s9AaTWFS92LaU=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I=
github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/flatbuffers/go v0.0.0-20230110200425-62e4d2e5b215 h1:HA3/6NDG1q6qLD2sCqDFT/ua/1/wctoLo7leuFcFdSE=
github.com/google/flatbuffers/go v0.0.0-20230110200425-62e4d2e5b215/go.mod h1:qmRCJW6OqZkfBt584Cmq1im0f4367CLrdABrq5lMOWo=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/goexpect v0.0.0-20210430020637-ab937bf7fd6f h1:7MmqygqdeJtziBUpm4Z9ThROFZUaVGaePMfcDnluf1E=
github.com/google/goexpect v0.0.0-20210430020637-ab937bf7fd6f/go.mod h1:n1ej5+FqyEytMt/mugVDZLIiqTMO+vsrgY+kM6ohzN0=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 h1:CVuJwN34x4xM2aT4sIKhmeib40NeBPhRihNjQmpJsA4=
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20251213031049-b05bdaca462f h1:HU1RgM6NALf/KW9HEY6zry3ADbDKcmpQ+hJedoNGQYQ=
github.com/google/pprof v0.0.0-20251213031049-b05bdaca462f/go.mod h1:67FPmZWbr+KDT/VlpWtw6sO9XSjpJmLuHpoLmWiTGgY=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
github.com/googleapis/enterprise-certificate-proxy v0.3.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ=
github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo=
github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
github.com/googleapis/go-sql-spanner v1.24.0 h1:K36siIx0KEka6Xttu2Rti9cBGK7mYQIoo6gUrW87uTU=
github.com/googleapis/go-sql-spanner v1.24.0/go.mod h1:ltBracyoOyIYJjTQcDxuYmJDfPgknsQMs63liLSF4AA=
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4=
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE=
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
github.com/jedib0t/go-pretty/v6 v6.2.7/go.mod h1:FMkOpgGD3EZ91cW8g/96RfxoV7bdeJyzXPYgz1L1ln0=
github.com/jedib0t/go-pretty/v6 v6.7.7 h1:Y1Id3lJ3k4UB8uwWWy3l8EVFnUlx5chR5+VbsofPNX0=
github.com/jedib0t/go-pretty/v6 v6.7.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jmrobles/h2go v0.5.0 h1:r+V3J1+8z5tExKHcVc8u0tXJfov391zEffJYALWKhA0=
github.com/jmrobles/h2go v0.5.0/go.mod h1:p7Vjfu/9f7g2RI1CkpwXnwqskV+47HviBg4C4FlW8eI=
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kenshaw/colors v0.2.3 h1:z/IIrevOGcc3r179X8WC1DOm5LgYAir0dw9cnVgtsvM=
github.com/kenshaw/colors v0.2.3/go.mod h1:Aok7+9KpR+qEwgCxDEoLBS6IGFhY1iRJIzbcv5ijewI=
github.com/kenshaw/rasterm v0.1.16 h1:IUmAe/C7YTY/em1irbNvd7cMR8HT6BSoN0931BzjmhQ=
github.com/kenshaw/rasterm v0.1.16/go.mod h1:VuAzsiWase7gJtuoKc4xPxdxq+ljBXrh/QNz6yy7uV0=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4=
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/mattn/go-adodb v0.0.1 h1:g/pk3V8m/WFX2IQRI58wAC24OQUFFXEiNsvs7dQ1WKg=
github.com/mattn/go-adodb v0.0.1/go.mod h1:jaSTRde4bohMuQgYQPxW3xRTPtX/cZKyxPrFVseJULo=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-sixel v0.0.8 h1:H0bBGQVOJoSvzvtTgCInxvg1IZiNlTcIIIx8A6uvjpQ=
github.com/mattn/go-sixel v0.0.8/go.mod h1:wbDSbrwpykVI1qEHyjZYsDgaJTwpVg9wSwmmh2slnBw=
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/microsoft/go-mssqldb v1.9.8 h1:d4IFMvF/o+HdpXUqbBfzHvn/NlFA75YGcfHUUvDFJEM=
github.com/microsoft/go-mssqldb v1.9.8/go.mod h1:eGSRSGAW4hKMy5YcAenhCDjIRm2rhqIdmmwgciMzLus=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mithrandie/csvq v1.18.1 h1:f7NB2scbb7xx2ffPduJ2VtZ85RpWXfvanYskAkGlCBU=
github.com/mithrandie/csvq v1.18.1/go.mod h1:MRJj7AtcXfk7jhNGxLuJGP3LORmh4lpiPWxQ7VyCRn8=
github.com/mithrandie/csvq-driver v1.7.0 h1:ejiavXNWwTPMyr3fJFnhcqd1L1cYudA0foQy9cZrqhw=
github.com/mithrandie/csvq-driver v1.7.0/go.mod h1:HcN3xL9UCJnBYA/AIQOOB/KlyfXAiYr5yxDmiwrGk5o=
github.com/mithrandie/go-file/v2 v2.1.0 h1:XA5Tl+73GXMDvgwSE3Sg0uC5FkLr3hnXs8SpUas0hyg=
github.com/mithrandie/go-file/v2 v2.1.0/go.mod h1:9YtTF3Xo59GqC1Pxw6KyGVcM/qubAMlxVsqI/u9r++c=
github.com/mithrandie/go-text v1.6.0 h1:8gOXTMPbMY8DJbKMTv8kHhADcJlDWXqS/YQH4SyWO6s=
github.com/mithrandie/go-text v1.6.0/go.mod h1:xCgj1xiNbI/d4xA9sLVvXkjh5B2tNx2ZT2/3rpmh8to=
github.com/mithrandie/ternary v1.1.1 h1:k/joD6UGVYxHixYmSR8EGgDFNONBMqyD373xT4QRdC4=
github.com/mithrandie/ternary v1.1.1/go.mod h1:0D9Ba3+09K2TdSZO7/bFCC0GjSXetCvYuYq0u8FY/1g=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/murfffi/gorich v0.3.0 h1:cRsCCTD0A2eyiAjeSMwyOkjALDsVzgJO0ghY+cXsDJY=
github.com/murfffi/gorich v0.3.0/go.mod h1:fozPmSzPmc1r0xnNtk3HE6xxxlzlBqo4rlChD/iJZcM=
github.com/nakagami/chacha20 v0.1.0 h1:2fbf5KeVUw7oRpAe6/A7DqvBJLYYu0ka5WstFbnkEVo=
github.com/nakagami/chacha20 v0.1.0/go.mod h1:xpoujepNFA7MvYLvX5xKHzlOHimDrLI9Ll8zfOJ0l2E=
github.com/nakagami/firebirdsql v0.9.15 h1:Mf05jaFI8+kjy6sBstsAu76zOkJ44AGd6cpApWNrp/0=
github.com/nakagami/firebirdsql v0.9.15/go.mod h1:bZKRs3rpHAjJgXAoc9YiPobTz3R22i41Zjo+llIS2B0=
github.com/nathan-fiscaletti/consolesize-go v0.0.0-20220204101620-317176b6684d h1:NqRhLdNVlozULwM1B3VaHhcXYSgrOAv8V5BE65om+1Q=
github.com/nathan-fiscaletti/consolesize-go v0.0.0-20220204101620-317176b6684d/go.mod h1:cxIIfNMTwff8f/ZvRouvWYF6wOoO7nj99neWSx2q/Es=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc=
github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runc v1.3.1 h1:c/yY0oh2wK7tzDuD56REnSxyU8ubh8hoAIOLGLrm4SM=
github.com/opencontainers/runc v1.3.1/go.mod h1:9wbWt42gV+KRxKRVVugNP6D5+PQciRbenB4fLVsqGPs=
github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw=
github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE=
github.com/paulmach/orb v0.12.0 h1:z+zOwjmG3MyEEqzv92UN49Lg1JFYx0L9GpGKNVDKk1s=
github.com/paulmach/orb v0.12.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0=
github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prestodb/presto-go-client v0.0.0-20240426182841-905ac40a1783 h1:1/uuAh1vatqywFmudA7PHVUc/Iu5W4iFft1r7MVubf8=
github.com/prestodb/presto-go-client v0.0.0-20240426182841-905ac40a1783/go.mod h1:9mH1KvIoMeUe/OIs6WCJGvrR15FvC0y+SSMkIQQkF3M=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/proullon/ramsql v0.1.4 h1:yTFRTn46gFH/kPbzCx+mGjuFlyTBUeDr3h2ldwxddl0=
github.com/proullon/ramsql v0.1.4/go.mod h1:CFGqeQHQpdRfWqYmWD3yXqPTEaHkF4zgXy1C6qDWc9E=
github.com/rekby/fixenv v0.6.1 h1:jUFiSPpajT4WY2cYuc++7Y1zWrnCxnovGCIX72PZniM=
github.com/rekby/fixenv v0.6.1/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sclgo/impala-go v1.4.1 h1:31r2MQ2NL+ia0Ik0LoHFOzTE8akzQ5SiHakyUXMWUXI=
github.com/sclgo/impala-go v1.4.1/go.mod h1:3qX0kRHejmoOjHkTtdLu/nxH0vitV9A+z+hGs15WTVw=
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sijms/go-ora/v2 v2.9.0 h1:+iQbUeTeCOFMb5BsOMgUhV8KWyrv9yjKpcK4x7+MFrg=
github.com/sijms/go-ora/v2 v2.9.0/go.mod h1:QgFInVi3ZWyqAiJwzBQA+nbKYKH77tdp1PYoCqhR2dU=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/snowflakedb/gosnowflake v1.19.0 h1:Oy/w5/hXiSJV09kgG9zpFZFjNRNvF5Cet7r6vzd87OQ=
github.com/snowflakedb/gosnowflake v1.19.0/go.mod h1:7D4+cLepOWrerVsH+tevW3zdMJ5/WrEN7ZceAC6xBv0=
github.com/soniakeys/quant v1.0.0 h1:N1um9ktjbkZVcywBVAAYpZYSHxEfJGzshHCxx/DaI0Y=
github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsImz6Y6zds=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/thda/tds v0.1.7 h1:s29kbnJK0agL3ps85A/sb9XS2uxgKF5UJ6AZjbyqXX4=
github.com/thda/tds v0.1.7/go.mod h1:isLIF1oZdXfkqVMJM8RyNrsjlHPlTKnPlnsBs7ngZcM=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/trinodb/trino-go-client v0.333.0 h1:+bsW8/uLFNF00MEL9JZJym94LlUnle25VgDlWGPEZos=
github.com/trinodb/trino-go-client v0.333.0/go.mod h1:91okdYtRUZoj3XJu/tqdzu11sNliQuN4A+vMFEB8GVE=
github.com/uber-go/tally v3.3.17+incompatible h1:nFHIuW3VQ22wItiE9kPXic8dEgExWOsVOHwpmoIvsMw=
github.com/uber-go/tally v3.3.17+incompatible/go.mod h1:YDTIBxdXyOU/sCWilKB4bgyufu1cEi0jdVnRdxvjnmU=
github.com/uber/athenadriver v1.1.15 h1:z/hivAcXmGgUCVoXgVvwwIzc4auTeF3TCmwyFTtd8NE=
github.com/uber/athenadriver v1.1.15/go.mod h1:RnKD7+9Aup8iuFfhK+I26U+z137IXWeoLaEZDepd0Eg=
github.com/vertica/vertica-sql-go v1.3.5 h1:IrfH2WIgzZ45yDHyjVFrXU2LuKNIjF5Nwi90a6cfgUI=
github.com/vertica/vertica-sql-go v1.3.5/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xo/dburl v0.24.2 h1:aK6ASamrFjKl76h/UCBecc0BPBi97+IVmw4YWxx0rno=
github.com/xo/dburl v0.24.2/go.mod h1:uazlaAQxj4gkshhfuuYyvwCBouOmNnG2aDxTCFZpmL4=
github.com/xo/echartsgoja v0.1.1 h1:9ZH7rhjovE/caGzWTFw1X5GItCsPw5eFV2Qwfgmjt7M=
github.com/xo/echartsgoja v0.1.1/go.mod h1:u2iiKyIA1H3URjh9+8Nltj8cP33R19JHasXhSz/tWHI=
github.com/xo/resvg v0.6.0 h1:GsovErv9JuOnGttOA8RhQcBI7DEEVpEiIEKBuJVRS4g=
github.com/xo/resvg v0.6.0/go.mod h1:xsIgOmL6UD2xRHIm2Laepjm/b4auoPMxAAqOHkvbSes=
github.com/xo/tblfmt v0.0.0-20190609041254-28c54ec42ce8/go.mod h1:3U5kKQdIhwACye7ml3acccHmjGExY9WmUGU7rnDWgv0=
github.com/xo/tblfmt v0.16.0 h1:Bh1+mEtA/tggn0YL0oIHgrtsRTskWTLKurG7JWYuk6k=
github.com/xo/tblfmt v0.16.0/go.mod h1:uA07YKaGqDkK0sRhtFleWAtzwF2i1pqFcM9zHI2S1g0=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ=
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/ydb-platform/ydb-go-genproto v0.0.0-20260128080146-c4ed16b24b37 h1:kUXMT/fM/DpDT66WQgRUf3I8VOAWjypkMf52W5PChwA=
github.com/ydb-platform/ydb-go-genproto v0.0.0-20260128080146-c4ed16b24b37/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I=
github.com/ydb-platform/ydb-go-sdk/v3 v3.127.2 h1:GyG12uYgpxp9Oeux8XDTVA+zDOwa/WIh5fGNIF5meW4=
github.com/ydb-platform/ydb-go-sdk/v3 v3.127.2/go.mod h1:stS1mQYjbJvwwYaYzKyFY9eMiuVXWWXQA6T+SpOLg9c=
github.com/yookoala/realpath v1.0.0 h1:7OA9pj4FZd+oZDsyvXWQvjn5oBdcHRTV44PpdMSuImQ=
github.com/yookoala/realpath v1.0.0/go.mod h1:gJJMA9wuX7AcqLy1+ffPatSCySA1FQ2S8Ya9AIoYBpE=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b/go.mod h1:IZpXDfkJ6tWD3PhBK5YzgQT+xJWh7OsdwiG8hA2MkO4=
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs=
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/detectors/gcp v1.41.0 h1:MBzEwqhroF0JK0DpTVYWDxsenxm6L4PqOEfA90uZ5AA=
go.opentelemetry.io/contrib/detectors/gcp v1.41.0/go.mod h1:5pSDD0v0t2HqUmPC5cBBc+nLQO4dLYWnzBNheXLBLgs=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.66.0 h1:w/o339tDd6Qtu3+ytwt+/jon2yjAs3Ot8Xq8pelfhSo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.66.0/go.mod h1:pdhNtM9C4H5fRdrnwO7NjxzQWhKSSxCHk/KluVqDVC0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0 h1:PnV4kVnw0zOmwwFkAzCN5O07fw1YOIQor120zrh0AVo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0/go.mod h1:ofAwF4uinaf8SXdVzzbL4OsxJ3VfeEg3f/F6CeF49/Y=
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=
go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90=
go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8=
go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y=
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/config v1.4.0/go.mod h1:aCyrMHmUAc/s2h9sv1koP84M9ZF/4K+g2oleyESO/Ig=
go.uber.org/dig v1.9.0/go.mod h1:X34SnWGr8Fyla9zQNO2GSO2D+TIuqB14OS8JhYocIyw=
go.uber.org/fx v1.12.0/go.mod h1:egT3Kyg1JFYQkvKLZ3EsykxkNrZxgXS+gKoKo7abERY=
go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/telemetry v0.0.0-20260213145524-e0ab670178e1 h1:QNaHp8YvpPswfDNxlCmJyeesxbGOgaKf41iT9/QrErY=
golang.org/x/telemetry v0.0.0-20260213145524-e0ab670178e1/go.mod h1:NuITXsA9cTiqnXtVk+/wrBT2Ja4X5hsfGOYRJ6kgYjs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190802003818-e9bb7d36c060/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191030062658-86caa796c7ab/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191114200427-caa0b0f7d508/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g=
google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI=
google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08=
google.golang.org/api v0.269.0 h1:qDrTOxKUQ/P0MveH6a7vZ+DNHxJQjtGm/uvdbdGXCQg=
google.golang.org/api v0.269.0/go.mod h1:N8Wpcu23Tlccl0zSHEkcAZQKDLdquxK+l9r2LkwAauE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE=
google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc=
google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw=
google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=
google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
google.golang.org/genproto v0.0.0-20260226221140-a57be14db171 h1:RxhCsti413yL0IjU9dVvuTbCISo8gs3RW1jPMStck+4=
google.golang.org/genproto v0.0.0-20260226221140-a57be14db171/go.mod h1:uhvzakVEqAuXU3TC2JCsxIRe5f77l+JySE3EqPoMyqM=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXtQ3VTbI=
gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4=
gopkg.in/jcmturner/gokrb5.v6 v6.1.1 h1:n0KFjpbuM5pFMN38/Ay+Br3l91netGSVqHPHEXeWUqk=
gopkg.in/jcmturner/gokrb5.v6 v6.1.1/go.mod h1:NFjHNLrHQiruory+EmqDXCGv6CrjkeYeA+bR9mIfNFk=
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/bigquery v1.2.0 h1:E94oEXErYb4uImcR8oiCjE1SP2VdnrL5f3d78PtFWNk=
gorm.io/driver/bigquery v1.2.0/go.mod h1:/5kcyb6RVIk/seff6YANAjB5aisE4oqY35x0Ix9iwXY=
gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
gotest.tools/gotestsum v1.13.0 h1:+Lh454O9mu9AMG1APV4o0y7oDYKyik/3kBOiCqiEpRo=
gotest.tools/gotestsum v1.13.0/go.mod h1:7f0NS5hFb0dWr4NtcsAsF0y1kzjEFfAil0HiBQJE03Q=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
modernc.org/b v1.1.0 h1:sFmr2MlofAtx5R0NC0btblNww5dqIHxXyT0SEiaTSIk=
modernc.org/b v1.1.0/go.mod h1:yF+wmBAFjebNdVqZNTeNfmnLaLqq91wozvDLcuXz+ck=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.31.0 h1:/bsaxqdgX3gy/0DboxcvWrc3NpzH+6wpFfI/ZaA/hrg=
modernc.org/ccgo/v4 v4.31.0/go.mod h1:jKe8kPBjIN/VdGTVqARTQ8N1gAziBmiISY8j5HoKwjg=
modernc.org/db v1.0.29 h1:irgsDp4SH0VqmdIeuyZmBtH79ETnxTqhC1O10nGe73g=
modernc.org/db v1.0.29/go.mod h1:6Q1hiJ/bHLUOOFhNMb9ujAes3GH10a7fTUTDQ7eMV5E=
modernc.org/file v1.0.19 h1:D0YRfPfuIgcGtozt+D8kwX8wN8TABAf1bV0mzhPUrrk=
modernc.org/file v1.0.19/go.mod h1:G9DjBo352Wgitb3GYSN1FAf3+N7IkpCjnvslO7knhA8=
modernc.org/fileutil v1.1.2/go.mod h1:HdjlliqRHrMAI4nVOvvpYVzVgvRSK7WnoCiG0GUWJNo=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/golex v1.1.0 h1:dmSaksHMd+y6NkBsRsCShNPRaSNCNH+abrVm5/gZic8=
modernc.org/golex v1.1.0/go.mod h1:2pVlfqApurXhR1m0N+WDYu6Twnc4QuvO4+U8HnwoiRA=
modernc.org/internal v1.0.8/go.mod h1:km71QBJPWkc1+LUldg2U9TJsKT6Q2QKHIykdEeCy/jw=
modernc.org/internal v1.1.9 h1:ldAWZkxkOTXXLKXRmEgEGIax7kzE4Q4JoIh6ouvDKgM=
modernc.org/internal v1.1.9/go.mod h1:5s+KF0W41ZrnzvPkq/p/8yorM8gKw5OmcnzNNZHfNUs=
modernc.org/libc v1.69.0 h1:YQJ5QMSReTgQ3QFmI0dudfjXIjCcYTUxcH8/9P9f0D8=
modernc.org/libc v1.69.0/go.mod h1:YfLLduUEbodNV2xLU5JOnRHBTAHVHsVW3bVYGw0ZCV4=
modernc.org/lldb v1.0.8 h1:gM0Lpmgtw0h/ylWQSxABvzJ++TZKhf1Q/uPAGBAM6aU=
modernc.org/lldb v1.0.8/go.mod h1:ybOcsZ/RNZo3q8fiGadQFRnD+1Jc+RWGcTPdeilCnUk=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/ql v1.4.31 h1:GdUqcdhULwfr0FLzjdqpYeX433yjXzU7xjXAild37K4=
modernc.org/ql v1.4.31/go.mod h1:LEOgRQzURtIngtsUP46v20Yr49WCXv5UAoMPbnVSXAA=
modernc.org/sortutil v1.1.1/go.mod h1:DTj/8BqjEBLZFVPYvEGDfFFg94SsfPxQ70R+SQJ98qA=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/zappy v1.0.9/go.mod h1:y2c4Hv5jzyBP179SxNmx5H/BM6cVgNIXPQv2bCeR6IM=
modernc.org/zappy v1.1.0 h1:cAf9HrymATNo2hYMc9c37y0tiZJYuKM2xa1ZAP8THUw=
modernc.org/zappy v1.1.0/go.mod h1:cxC0dWAgZuyMsJ+KL3ZBgo3twyKGBB/0By/umSZE2bQ=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sqlflow.org/gohive v0.0.0-20240730014249-8960223660e2 h1:zvkshqW4meDpDadU5rhwejwJrZVCfxo5qpJ4NoZeGbE=
sqlflow.org/gohive v0.0.0-20240730014249-8960223660e2/go.mod h1:OAU0/vkmdKfZ363QgGTChI35KIBsS63sZWDNWcFFcBM=
sqlflow.org/gomaxcompute v0.0.0-20210805062559-c14ae028b44c h1:Zo3qlfUn/rlMx9vWHpGE/luEtweuXHwrYbrFZwTG978=
sqlflow.org/gomaxcompute v0.0.0-20210805062559-c14ae028b44c/go.mod h1:MxRFJp6UEk1OfnnVOIL3Jc7ROBH0dOpwF/J14A9LNdM=
================================================
FILE: handler/handler.go
================================================
// Package handler provides a input process handler implementation for usql.
package handler
import (
"bufio"
"bytes"
"context"
"database/sql"
"errors"
"fmt"
"io"
"log"
"maps"
"net/url"
"os"
"os/exec"
"os/signal"
"os/user"
"path"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
"syscall"
"time"
"unicode"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/formatters"
"github.com/alecthomas/chroma/v2/styles"
"github.com/go-git/go-billy/v5"
"github.com/xo/dburl"
"github.com/xo/dburl/passfile"
"github.com/xo/echartsgoja"
"github.com/xo/resvg"
"github.com/xo/tblfmt"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/completer"
"github.com/xo/usql/drivers/metadata"
"github.com/xo/usql/env"
"github.com/xo/usql/metacmd"
"github.com/xo/usql/metacmd/charts"
"github.com/xo/usql/rline"
"github.com/xo/usql/stmt"
ustyles "github.com/xo/usql/styles"
"github.com/xo/usql/text"
)
// Handler is a input process handler.
//
// Glues together usql's components to provide a "read-eval-print loop" (REPL)
// for usql's interactive command-line and manages most of the core/high-level logic.
//
// Manages the active statement buffer, application IO, executing/querying SQL
// statements, and handles backslash (\) meta commands encountered in the input
// stream.
type Handler struct {
// l is the readline handler.
l rline.IO
// user is the user.
user *user.User
// wd is the working directory.
wd string
// charts is the charts filesystem.
charts billy.Filesystem
// nopw indicates not asking for password.
nopw bool
// timing of every command executed.
timing bool
// singleLineMode is single line mode.
singleLineMode bool
// buf is the query statement buffer.
buf *stmt.Stmt
// lastExec is the last executed query statement.
lastExec string
// lastExecPrefix is the last executed query statement prefix.
lastExecPrefix string
// lastPrint is the last executed printable query statement.
lastPrint string
// lastRaw is the last executed raw query statement.
lastRaw string
// batch indicates a batch has been started.
batch bool
// batchEnd is the batch end string.
batchEnd string
// bind are bound values for that will be used for statement execution.
bind []interface{}
// u is the active connection information.
u *dburl.URL
// db is the active database connection.
db *sql.DB
// tx is the active transaction, if any.
tx *sql.Tx
// out file or pipe
out io.WriteCloser
}
// New creates a new input handler.
func New(l rline.IO, user *user.User, wd string, charts billy.Filesystem, nopw bool) *Handler {
f, iactive := l.Next, l.Interactive()
if iactive {
f = func() ([]rune, error) {
// next line
r, err := l.Next()
if err != nil {
return nil, err
}
// save history
_ = l.Save(string(r))
return r, nil
}
}
h := &Handler{
l: l,
user: user,
wd: wd,
charts: charts,
nopw: nopw,
buf: stmt.New(f),
}
if iactive {
l.SetOutput(h.outputHighlighter)
l.Completer(completer.NewDefaultCompleter(completer.WithConnStrings(h.connStrings())))
}
return h
}
// GetTiming gets the timing toggle.
func (h *Handler) GetTiming() bool {
return h.timing
}
// SetTiming sets the timing toggle.
func (h *Handler) SetTiming(timing bool) {
h.timing = timing
}
// SetSingleLineMode sets the single line mode toggle.
func (h *Handler) SetSingleLineMode(singleLineMode bool) {
h.singleLineMode = singleLineMode
}
// Run executes queries and commands.
func (h *Handler) Run() error {
stdout, stderr, iactive := h.l.Stdout(), h.l.Stderr(), h.l.Interactive()
// display welcome info
if iactive && env.Get("QUIET") == "off" {
// logo
if typ := env.TermGraphics(); typ.Available() {
if err := typ.Encode(stdout, text.Logo); err != nil {
return err
}
}
// welcome text
fmt.Fprintln(stdout, text.WelcomeDesc)
fmt.Fprintln(stdout)
}
var cmd string
var paramstr string
var err error
var opt metacmd.Option
var cont bool
var lastErr error
var execute bool
for {
execute = false
// set prompt
if iactive {
h.l.Prompt(h.Prompt(env.Get("PROMPT1")))
}
// read next statement/command
switch cmd, paramstr, err = h.buf.Next(env.Untick(h.user, env.Vars(), false)); {
case h.singleLineMode && err == nil:
execute = h.buf.Len != 0
case err == rline.ErrInterrupt:
h.buf.Reset(nil)
continue
case err == io.EOF:
return lastErr
case err != nil:
return err
case cmd != "":
opt, cont, lastErr = h.apply(stdout, stderr, strings.TrimPrefix(cmd, `\`), paramstr)
}
if cont {
continue
}
// help, exit, quit intercept
if iactive && len(h.buf.Buf) >= 4 {
i, first := lastIndex(h.buf.Buf, '\n'), false
if i == -1 {
i, first = 0, true
}
if s := strings.ToLower(helpQuitExitRE.FindString(string(h.buf.Buf[i:]))); s != "" {
switch s {
case "help":
s = text.HelpDescShort
if first {
s = text.HelpDesc
h.buf.Reset(nil)
}
case "quit", "exit":
s = text.QuitDesc
if first {
return nil
}
}
fmt.Fprintln(stdout, s)
}
}
// quit
if opt.Quit {
if h.out != nil {
h.out.Close()
}
return nil
}
// execute buf
if execute || h.buf.Ready() || opt.Exec != metacmd.ExecNone {
// intercept batch query
if h.u != nil {
typ, end, batch := drivers.IsBatchQueryPrefix(h.u, h.buf.Prefix)
switch {
case h.batch && batch:
err = fmt.Errorf("cannot perform %s in existing batch", typ)
lastErr = WrapErr(h.buf.String(), err)
fmt.Fprintln(stderr, "error:", err)
continue
// cannot use \g* while accumulating statements for batch queries
case h.batch && typ != h.batchEnd && opt.Exec != metacmd.ExecNone:
err = errors.New("cannot force batch execution")
lastErr = WrapErr(h.buf.String(), err)
fmt.Fprintln(stderr, "error:", err)
continue
case batch:
h.batch, h.batchEnd = true, end
case h.batch:
var lend string
if len(h.lastExec) != 0 {
lend = "\n"
}
// append to last
h.lastExec += lend + h.buf.String()
h.lastExecPrefix = h.buf.Prefix
h.lastPrint += lend + h.buf.PrintString()
h.lastRaw += lend + h.buf.RawString()
h.buf.Reset(nil)
// break
if h.batchEnd != typ {
continue
}
h.lastExecPrefix = h.batchEnd
h.batch, h.batchEnd = false, ""
}
}
if h.buf.Len != 0 {
h.lastExec, h.lastExecPrefix, h.lastPrint, h.lastRaw = h.buf.String(), h.buf.Prefix, h.buf.PrintString(), h.buf.RawString()
h.buf.Reset(nil)
}
// log.Printf(">> PROCESS EXECUTE: (%s) `%s`", h.lastPrefix, h.last)
if !h.batch && h.lastExec != "" && h.lastExec != ";" {
// force a transaction for batched queries for certain drivers
var forceBatch bool
if h.u != nil {
_, _, forceBatch = drivers.IsBatchQueryPrefix(h.u, stmt.FindPrefix(h.lastExec, true, true, true))
forceBatch = forceBatch && drivers.BatchAsTransaction(h.u)
}
// execute
out := stdout
if h.out != nil {
out = h.out
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
if err = h.Execute(ctx, out, opt, h.lastExecPrefix, h.lastExec, forceBatch, h.unbind()...); err != nil {
lastErr = WrapErr(h.lastExec, err)
if env.Get("ON_ERROR_STOP") == "on" {
if iactive {
fmt.Fprintln(stderr, "error:", err)
h.buf.Reset([]rune{}) // empty the buffer so no other statements are run
continue
} else {
stop()
return err
}
} else {
fmt.Fprintln(stderr, "error:", err)
}
}
stop()
}
}
}
}
// apply applies the command against the handler.
func (h *Handler) apply(stdout, stderr io.Writer, cmd, paramstr string) (metacmd.Option, bool, error) {
// cmd = strings.TrimPrefix(cmd, `\`)
params := stmt.NewParams(paramstr)
// decode
f, err := metacmd.Decode(cmd, params)
if err != nil {
switch err = WrapErr(cmd, err); {
case err == text.ErrUnknownCommand:
fmt.Fprintln(stderr, fmt.Sprintf(text.InvalidCommand, cmd))
case err == text.ErrMissingRequiredArgument:
fmt.Fprintln(stderr, fmt.Sprintf(text.MissingRequiredArg, cmd))
default:
fmt.Fprintln(stderr, "error:", err)
}
return metacmd.Option{}, true, err
}
// run
opt, err := f(h)
if err != nil && err != rline.ErrInterrupt {
fmt.Fprintln(stderr, "error:", err)
return metacmd.Option{}, true, WrapErr(cmd, err)
}
loop:
// print unused command parameters
for {
switch arg, ok, err := params.Arg(); {
case err != nil:
fmt.Fprintln(stderr, "error:", err)
case !ok:
break loop
default:
fmt.Fprintln(stdout, fmt.Sprintf(text.ExtraArgumentIgnored, cmd, arg))
}
}
return opt, false, nil
}
// outputHighlighter returns s as a highlighted string, based on the current
// buffer and syntax highlighting settings.
func (h *Handler) outputHighlighter(s string) string {
// bail when string is empty (ie, contains no printable, non-space
// characters) or if syntax highlighting is not enabled
if empty(s) || env.Get("SYNTAX_HL") != "true" {
return s
}
// count end lines
var endl string
if m := lineendRE.FindStringSubmatch(s); m != nil {
s = strings.TrimSuffix(s, m[0])
endl += m[0]
}
// leading whitespace
var leading string
// capture current query statement buffer
orig := h.buf.RawString()
full := orig
if full != "" {
full += "\n"
} else {
// get leading whitespace
if i := strings.IndexFunc(s, func(r rune) bool {
return !isSpaceOrControl(r)
}); i != -1 {
leading = s[:i]
}
}
full += s
// setup statement parser
st := drivers.NewStmt(h.u, func() func() ([]rune, error) {
y := strings.Split(orig, "\n")
if y[0] == "" {
y[0] = s
} else {
y = append(y, s)
}
return func() ([]rune, error) {
if len(y) > 0 {
z := y[0]
y = y[1:]
return []rune(z), nil
}
return nil, io.EOF
}
}())
// accumulate all "active" statements in buffer, breaking either at
// EOF or when a \ cmd has been encountered
var err error
var cmd, final string
loop:
for {
cmd, _, err = st.Next(env.Untick(h.user, env.Vars(), false))
switch {
case err != nil && err != io.EOF:
return s + endl
case err == io.EOF:
break loop
}
if st.Ready() || cmd != "" {
final += st.RawString()
st.Reset(nil)
// grab remaining whitespace to add to final
l := len(final)
// find first non empty character
if i := strings.IndexFunc(full[l:], func(r rune) bool {
return !isSpaceOrControl(r)
}); i != -1 {
final += full[l : l+i]
}
}
}
if !st.Ready() && cmd == "" {
final += st.RawString()
}
final = leading + final
// determine whatever is remaining after "active"
var remaining string
if fnl := len(final); fnl < len(full) {
remaining = full[fnl:]
}
// this happens when a read line is empty and/or has only
// whitespace and a \ cmd
if s == remaining {
return s + endl
}
// highlight entire final accumulated buffer
b := new(bytes.Buffer)
if err := h.Highlight(b, final); err != nil {
return s + endl
}
colored := b.String()
// return only last line plus whatever remaining string (ie, after
// a \ cmd) and the end line count
ss := strings.Split(colored, "\n")
return lastcolor(colored) + ss[len(ss)-1] + remaining + endl
}
// Execute executes a query against the connected database.
func (h *Handler) Execute(ctx context.Context, w io.Writer, opt metacmd.Option, prefix, sqlstr string, forceTrans bool, bind ...interface{}) error {
if h.db == nil {
return text.ErrNotConnected
}
// determine type and pre process string
prefix, sqlstr, qtyp, err := drivers.Process(h.u, prefix, sqlstr)
if err != nil {
return drivers.WrapErr(h.u.Driver, err)
}
// start a transaction if forced
if forceTrans {
if err = h.BeginTx(ctx, nil); err != nil {
return err
}
}
f := h.doExecSingle
switch opt.Exec {
case metacmd.ExecExec:
f = h.doExecExec
case metacmd.ExecSet:
f = h.doExecSet
case metacmd.ExecWatch:
f = h.doExecWatch
case metacmd.ExecChart:
f = h.doExecChart
}
if err = drivers.WrapErr(h.u.Driver, f(ctx, w, opt, prefix, sqlstr, qtyp, bind)); err != nil {
if forceTrans {
defer h.tx.Rollback()
h.tx = nil
}
return err
}
if forceTrans {
return h.Commit()
}
return nil
}
// Reset resets the handler's query statement buffer.
func (h *Handler) Reset(r []rune) {
h.buf.Reset(r)
h.lastExec, h.lastExecPrefix, h.lastPrint, h.lastRaw, h.batch, h.batchEnd = "", "", "", "", false, ""
}
// Bind sets the bind parameters for the next query execution.
func (h *Handler) Bind(bind []interface{}) {
h.bind = bind
}
// unbind returns the bind parameters.
func (h *Handler) unbind() []interface{} {
v := h.bind
h.bind = nil
return v
}
// Prompt parses a prompt.
//
// NOTE: the documentation below is INCORRECT, as it is just copied from
// https://www.postgresql.org/docs/current/app-psql.html#APP-PSQL-PROMPTING
//
// TODO/FIXME: complete this functionality (from psql documentation):
//
// %M - The full host name (with domain name) of the database server, or
// [local] if the connection is over a Unix domain socket, or
// [local:/dir/name], if the Unix domain socket is not at the compiled in
// default location.
//
// %m - The host name of the database server, truncated at the first dot, or
// [local] if the connection is over a Unix domain socket.
//
// %> - The port number at which the database server is listening.
//
// %n - The database session user name. (The expansion of this value might
// change during a database session as the result of the command SET SESSION
// AUTHORIZATION.)
//
// %/ - The name of the current database.
//
// %~ - Like %/, but the output is ~ (tilde) if the database is your default
// database.
//
// %# - If the session user is a database superuser, then a #, otherwise a >.
// (The expansion of this value might change during a database session as the
// result of the command SET SESSION AUTHORIZATION.)
//
// %p - The process ID of the backend currently connected to.
//
// %R - In prompt 1 normally =, but @ if the session is in an inactive branch
// of a conditional block, or ^ if in single-line mode, or ! if the session is
// disconnected from the database (which can happen if \connect fails). In
// prompt 2 %R is replaced by a character that depends on why psql expects
// more input: - if the command simply wasn't terminated yet, but * if there
// is an unfinished /* ... */ comment, a single quote if there is an
// unfinished quoted string, a double quote if there is an unfinished quoted
// identifier, a dollar sign if there is an unfinished dollar-quoted string,
// or ( if there is an unmatched left parenthesis. In prompt 3 %R doesn't
// produce anything.
//
// %x - Transaction status: an empty string when not in a transaction block,
// or * when in a transaction block, or ! when in a failed transaction block,
// or ? when the transaction state is indeterminate (for example, because
// there is no connection).
//
// %l - The line number inside the current statement, starting from 1.
//
// %digits - The character with the indicated octal code is substituted.
//
// %:name: - The value of the psql variable name. See Variables, above, for
// details.
//
// %`command` - The output of command, similar to ordinary “back-tick”
// substitution.
//
// %[ ... %] - Prompts can contain terminal control characters which, for
// example, change the color, background, or style of the prompt text, or
// change the title of the terminal window. In order for the line editing
// features of Readline to work properly, these non-printing control
// characters must be designated as invisible by surrounding them with %[ and
// %]. Multiple pairs of these can occur within the prompt. For example:
//
// testdb=> \set PROMPT1 '%[%033[1;33;40m%]%n@%/%R%[%033[0m%]%# '
//
// results in a boldfaced (1;) yellow-on-black (33;40) prompt on
// VT100-compatible, color-capable terminals.
//
// %w - Whitespace of the same width as the most recent output of PROMPT1.
// This can be used as a PROMPT2 setting, so that multi-line statements are
// aligned with the first line, but there is no visible secondary prompt.
//
// To insert a percent sign into your prompt, write %%. The default prompts are
// '%/%R%x%# ' for prompts 1 and 2, and '>> ' for prompt 3.
func (h *Handler) Prompt(prompt string) string {
r, connected := []rune(prompt), h.db != nil
end := len(r)
var buf []byte
for i := 0; i < end; i++ {
if r[i] != '%' {
buf = append(buf, string(r[i])...)
continue
}
switch grab(r, i+1, end) {
case '%': // literal
buf = append(buf, '%')
case 'S': // short driver name
if connected {
s := dburl.ShortAlias(h.u.Scheme)
if s == "" {
s = dburl.ShortAlias(h.u.Driver)
}
if s == "" {
s = text.UnknownShortAlias
}
buf = append(buf, s+":"...)
} else {
buf = append(buf, text.NotConnected...)
}
case 'u': // dburl short
if connected {
buf = append(buf, h.u.Short()...)
} else {
buf = append(buf, text.NotConnected...)
}
case 'M': // full host name with domain
if connected {
buf = append(buf, h.u.Hostname()...)
}
case 'm': // host name truncated at first dot, or [local] if it's a domain socket
if connected {
s := h.u.Hostname()
if i := strings.Index(s, "."); i != -1 {
s = s[:i]
}
buf = append(buf, s...)
}
case '>': // the port number
if connected {
s := h.u.Port()
if s != "" {
s = ":" + s
}
buf = append(buf, s...)
}
case 'N': // database user
if connected && h.u.User != nil {
s := h.u.User.Username()
if s != "" {
buf = append(buf, s+"@"...)
}
}
case 'n': // database user
if connected && h.u.User != nil {
buf = append(buf, h.u.User.Username()...)
}
case '/': // database name
switch {
case connected && h.u.Opaque != "":
buf = append(buf, h.u.Opaque...)
case connected && h.u.Path != "" && h.u.Path != "/":
buf = append(buf, h.u.Path...)
}
case 'O':
if connected {
buf = append(buf, h.u.Opaque...)
}
case 'o':
if connected {
buf = append(buf, filepath.Base(h.u.Opaque)...)
}
case 'P':
if connected {
buf = append(buf, h.u.Path...)
}
case 'p':
if connected {
buf = append(buf, path.Base(h.u.Path)...)
}
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
j := i + 1
base := 10
if grab(r, j, end) == '0' {
j++
base = 8
}
if grab(r, j, end) == 'x' {
j++
base = 16
}
i = j
for unicode.IsDigit(grab(r, i+1, end)) {
i++
}
n, err := strconv.ParseInt(string(r[j:i+1]), base, 16)
if err == nil {
buf = append(buf, byte(n))
}
i--
case '~': // like %/ but ~ when default database
case '#': // when superuser, a #, otherwise >
if h.tx != nil || h.batch {
buf = append(buf, '~')
} else {
buf = append(buf, '>')
}
// case 'p': // the process id of the connected backend -- never going to be supported
case 'R': // statement state
buf = append(buf, h.buf.State()...)
case 'x': // empty when not in a transaction block, * in transaction block, ! in failed transaction block, or ? when indeterminate
case 'l': // line number
case ':': // variable value
case '`': // value of the evaluated command
case '[', ']':
case 'w':
}
i++
}
return string(buf)
}
// IO returns the io for the handler.
func (h *Handler) IO() rline.IO {
return h.l
}
// User returns the user for the handler.
func (h *Handler) User() *user.User {
return h.user
}
// URL returns the URL for the handler.
func (h *Handler) URL() *dburl.URL {
return h.u
}
// DB returns the sql.DB for the handler.
func (h *Handler) DB() drivers.DB {
if h.tx != nil {
return h.tx
}
return h.db
}
// LastExec returns the last executed statement.
func (h *Handler) LastExec() string {
return h.lastExec
}
// LastPrint returns the last printable statement.
func (h *Handler) LastPrint() string {
return h.lastPrint
}
// LastRaw returns the last raw (non-interpolated) executed statement.
func (h *Handler) LastRaw() string {
return h.lastRaw
}
// Buf returns the current query statement buffer.
func (h *Handler) Buf() *stmt.Stmt {
return h.buf
}
// Highlight highlights using the current environment settings.
func (h *Handler) Highlight(w io.Writer, buf string) error {
// create lexer, formatter, styler
l := chroma.Coalesce(drivers.Lexer(h.u))
f := formatters.Get(env.Get("SYNTAX_HL_FORMAT"))
s := styles.Get(env.Get("SYNTAX_HL_STYLE"))
// override background
if env.Get("SYNTAX_HL_OVERRIDE_BG") != "false" {
s = ustyles.Get(env.Get("SYNTAX_HL_STYLE"))
}
// tokenize stream
it, err := l.Tokenise(nil, buf)
if err != nil {
return err
}
// write formatted output
return f.Format(w, s, it)
}
// Open handles opening a specified database URL, passing either a single
// string in the form of a URL, or more than one string, in which case the
// first string is treated as a driver name, and the remaining strings are
// joined (with a space) and passed as a DSN to sql.Open.
//
// If there is only one parameter, and it is not a well formatted URL, but
// appears to be a file on disk, then an attempt will be made to open it with
// an appropriate driver (mysql, postgres, sqlite3) depending on the type (unix
// domain socket, directory, or regular file, respectively).
func (h *Handler) Open(ctx context.Context, params ...string) error {
if len(params) == 0 || params[0] == "" {
return nil
}
if h.tx != nil {
return text.ErrPreviousTransactionExists
}
if len(params) == 1 {
if v, ok := env.Vars().GetConn(params[0]); ok {
params = v
}
}
if len(params) < 2 {
dsn := params[0]
// parse dsn
u, err := dburl.Parse(dsn)
if err != nil {
return err
}
h.u = u
// force parameters
h.forceParams(h.u)
} else {
h.u = &dburl.URL{
Driver: params[0],
DSN: strings.Join(params[1:], " "),
}
}
// open connection
var err error
h.db, err = drivers.Open(ctx, h.u, h.GetOutput, h.l.Stderr)
if err != nil && !drivers.IsPasswordErr(h.u, err) {
defer h.Close()
return err
}
// set buffer options
drivers.ConfigStmt(h.u, h.buf)
// force error/check connection
if err == nil {
if err = drivers.Ping(ctx, h.u, h.db); err == nil {
if h.l.Interactive() {
h.l.Completer(drivers.NewCompleter(ctx, h.u, h.db, readerOpts(), completer.WithConnStrings(h.connStrings())))
}
return h.Version(ctx)
}
}
// bail without getting password
if h.nopw || !drivers.IsPasswordErr(h.u, err) || len(params) > 1 || !h.l.Interactive() {
defer h.Close()
return err
}
// print the error
fmt.Fprintln(h.l.Stderr(), "error:", err)
// otherwise, try to collect a password ...
dsn, err := h.Password(params[0])
if err != nil {
// close connection
defer h.Close()
return err
}
// reconnect
return h.Open(ctx, dsn)
}
func (h *Handler) connStrings() []string {
entries, err := passfile.Entries(h.user.HomeDir, text.PassfileName)
if err != nil {
// ignore the error as this is only used for completer
// and it'll be reported again when trying to force params before opening a conn
entries = nil
}
available := drivers.Available()
names := make([]string, 0, len(available)+len(entries))
for schema := range available {
_, aliases := dburl.SchemeDriverAndAliases(schema)
// TODO should we create all combinations of space, :, :// and +transport ?
names = append(names, schema)
names = append(names, aliases...)
}
for _, entry := range entries {
if entry.Protocol == "*" {
continue
}
user, host, port, dbname := "", "", "", ""
if entry.Username != "*" {
user = entry.Username + "@"
if entry.Host != "*" {
host = entry.Host
if entry.Port != "*" {
port = ":" + entry.Port
}
if entry.DBName != "*" {
dbname = "/" + entry.DBName
}
}
}
names = append(names, entry.Protocol+"://"+user+host+port+dbname)
}
return append(names, slices.Sorted(maps.Keys(env.Vars().Conn()))...)
}
// forceParams forces connection parameters on a database URL, adding any
// driver specific required parameters, and the username/password when a
// matching entry exists in the PASS file.
func (h *Handler) forceParams(u *dburl.URL) {
// force driver parameters
drivers.ForceParams(u)
// see if password entry is present
user, err := passfile.Match(u, h.user.HomeDir, text.PassfileName)
switch {
case err != nil:
fmt.Fprintln(h.l.Stderr(), "error:", err)
case user != nil:
u.User = user
}
// copy back to u
z, _ := dburl.Parse(u.String())
*u = *z
}
// Password collects a password from input, and returns a modified DSN
// including the collected password.
func (h *Handler) Password(dsn string) (string, error) {
switch conn, ok := env.Vars().GetConn(dsn); {
case dsn == "":
return "", text.ErrMissingDSN
case ok && len(conn) < 2:
return "", text.ErrNamedConnectionIsNotAURL
case ok:
dsn = conn[0]
}
u, err := dburl.Parse(dsn)
if err != nil {
return "", err
}
user := h.user.Username
if u.User != nil {
user = u.User.Username()
}
pass, err := h.l.Password(text.EnterPassword)
if err != nil {
return "", err
}
u.User = url.UserPassword(user, pass)
return u.String(), nil
}
// Close closes the database connection if it is open.
func (h *Handler) Close() error {
if h.tx != nil {
return text.ErrPreviousTransactionExists
}
if h.db != nil {
err := h.db.Close()
drv := h.u.Driver
h.db, h.u = nil, nil
return drivers.WrapErr(drv, err)
}
return nil
}
// ReadVar reads a variable from the interactive prompt, saving it to
// environment variables.
func (h *Handler) ReadVar(typ, prompt string) (string, error) {
var masked bool
// check type
switch typ {
case "password":
masked = true
case "string", "int", "uint", "float", "bool":
default:
return "", text.ErrInvalidType
}
var v string
var err error
if masked {
if prompt == "" {
prompt = text.EnterPassword
}
v, err = h.l.Password(prompt)
} else {
h.l.Prompt(prompt)
var r []rune
r, err = h.l.Next()
v = string(r)
}
switch typ {
case "int":
_, err = strconv.ParseInt(v, 10, 64)
case "uint":
_, err = strconv.ParseUint(v, 10, 64)
case "float":
_, err = strconv.ParseFloat(v, 64)
case "bool":
var b bool
if b, err = strconv.ParseBool(v); err == nil {
v = fmt.Sprintf("%t", b)
}
}
if err != nil {
errstr := err.Error()
if i := strings.LastIndex(errstr, ":"); i != -1 {
errstr = strings.TrimSpace(errstr[i+1:])
}
return "", fmt.Errorf(text.InvalidValue, typ, v, errstr)
}
return v, nil
}
// ChangePassword changes a password for the user.
func (h *Handler) ChangePassword(user string) (string, error) {
if h.db == nil {
return "", text.ErrNotConnected
}
if !h.l.Interactive() {
return "", text.ErrNotInteractive
}
var err error
if err = drivers.CanChangePassword(h.u); err != nil {
return "", err
}
var newpw, newpw2, oldpw string
// ask for previous password
if user == "" && drivers.RequirePreviousPassword(h.u) {
oldpw, err = h.l.Password(text.EnterPreviousPassword)
if err != nil {
return "", err
}
}
// attempt to get passwords
for i := 0; i < 3; i++ {
if newpw, err = h.l.Password(text.NewPassword); err != nil {
return "", err
}
if newpw2, err = h.l.Password(text.ConfirmPassword); err != nil {
return "", err
}
if newpw == newpw2 {
break
}
fmt.Fprintln(h.l.Stderr(), text.PasswordsDoNotMatch)
}
// verify passwords match
if newpw != newpw2 {
return "", text.ErrPasswordAttemptsExhausted
}
return drivers.ChangePassword(h.u, h.DB(), user, newpw, oldpw)
}
// Version prints the database version information after a successful connection.
func (h *Handler) Version(ctx context.Context) error {
if env.Get("SHOW_HOST_INFORMATION") != "true" || !h.l.Interactive() {
return nil
}
if h.db == nil {
return text.ErrNotConnected
}
ver, err := drivers.Version(ctx, h.u, h.DB())
switch {
case err != nil:
ver = fmt.Sprintf("", err)
case ver == "":
ver = ""
}
h.Print(text.ConnInfo, h.u.Driver, ver)
return nil
}
// Print formats according to a format specifier and writes to handler's standard output.
func (h *Handler) Print(s string, v ...interface{}) {
if env.Get("QUIET") == "on" {
return
}
fmt.Fprintln(h.l.Stdout(), fmt.Sprintf(s, v...))
}
// doExecWatch repeatedly executes a query against the database.
func (h *Handler) doExecWatch(ctx context.Context, w io.Writer, opt metacmd.Option, prefix, sqlstr string, qtyp bool, bind []interface{}) error {
for {
// the actual output that psql has: "Mon Jan 2006 3:04:05 PM MST" -- which is _slightly_ different than RFC1123
// fmt.Fprintf(w, "%s (every %fs)\n\n", time.Now().Format("Mon Jan 2006 3:04:05 PM MST"), float64(opt.Watch)/float64(time.Second))
fmt.Fprintf(w, "%s (every %v)\n", time.Now().Format(time.RFC1123), opt.Watch)
fmt.Fprintln(w)
if err := h.doExecSingle(ctx, w, opt, prefix, sqlstr, qtyp, bind); err != nil {
return err
}
select {
case <-ctx.Done():
if err := ctx.Err(); err != nil && !errors.Is(err, context.Canceled) {
return err
}
return nil
case <-time.After(opt.Watch):
}
}
}
// doExecChart executes a single query against the database, displaying its output as a chart.
func (h *Handler) doExecChart(ctx context.Context, w io.Writer, opt metacmd.Option, prefix, sqlstr string, qtyp bool, bind []interface{}) error {
stdout, _, _ := h.l.Stdout(), h.l.Stderr(), h.l.Interactive()
typ := env.TermGraphics()
if !typ.Available() {
return text.ErrGraphicsNotSupported
}
if _, ok := opt.Params["help"]; ok {
fmt.Fprintln(stdout, text.ChartUsage)
return nil
}
cfg, err := charts.ParseArgs(opt.Params)
if err != nil {
return err
}
start := time.Now()
// query
rows, err := h.DB().QueryContext(ctx, sqlstr, bind...)
if err != nil {
return err
}
// get cols
cols, err := drivers.Columns(h.u, rows)
if err != nil {
return err
}
// process row(s)
transposed := make([][]string, len(cols))
clen, tfmt := len(cols), env.Vars().PrintTimeFormat()
for rows.Next() {
row, err := h.scan(rows, clen, tfmt)
if err != nil {
return err
}
for i := range row {
transposed[i] = append(transposed[i], row[i])
}
}
// display
c, err := charts.MakeChart(cfg, cols, transposed)
if err != nil {
return err
}
data, err := c.ToEcharts()
if err != nil {
return err
}
echarts := echartsgoja.New(echartsgoja.WithWidthHeight(cfg.W, cfg.H))
res, err := echarts.RenderOptions(ctx, data)
if err != nil {
return err
}
if cfg.File != "" {
fmt.Println("writing to", cfg.File)
return os.WriteFile(cfg.File, []byte(res), 0o644)
}
img, err := resvg.Render([]byte(res), resvg.WithBackground(cfg.Background))
if err != nil {
return err
}
if err := typ.Encode(stdout, img); err != nil {
return err
}
if h.timing {
d := time.Since(start)
s := text.TimingDesc
v := []interface{}{float64(d.Microseconds()) / 1000}
if d > 1*time.Second {
s += " (%v)"
v = append(v, d.Round(1*time.Millisecond))
}
fmt.Fprintln(h.l.Stdout(), fmt.Sprintf(s, v...))
}
return nil
}
// doExecSingle executes a single query against the database based on its query type.
func (h *Handler) doExecSingle(ctx context.Context, w io.Writer, opt metacmd.Option, prefix, sqlstr string, qtyp bool, bind []interface{}) error {
// exec or query
f := h.doExec
if qtyp {
f = h.doQuery
}
// exec
start := time.Now()
if err := f(ctx, w, opt, prefix, sqlstr, bind); err != nil {
return err
}
if h.timing {
d := time.Since(start)
s := text.TimingDesc
v := []interface{}{float64(d.Microseconds()) / 1000}
if d > 1*time.Second {
s += " (%v)"
v = append(v, d.Round(1*time.Millisecond))
}
fmt.Fprintln(h.l.Stdout(), fmt.Sprintf(s, v...))
}
return nil
}
// doExecSet executes a SQL query, setting all returned columns as variables.
func (h *Handler) doExecSet(ctx context.Context, w io.Writer, opt metacmd.Option, prefix, sqlstr string, _ bool, bind []interface{}) error {
// query
rows, err := h.DB().QueryContext(ctx, sqlstr, bind...)
if err != nil {
return err
}
// get cols
cols, err := drivers.Columns(h.u, rows)
if err != nil {
return err
}
// process row(s)
var i int
var row []string
clen, tfmt := len(cols), env.Vars().PrintTimeFormat()
for rows.Next() {
if i == 0 {
row, err = h.scan(rows, clen, tfmt)
if err != nil {
return err
}
}
i++
}
if i > 1 {
return text.ErrTooManyRows
}
// set vars
for i, c := range cols {
n := opt.Params["prefix"] + c
if err = env.ValidIdentifier(n); err != nil {
return fmt.Errorf(text.CouldNotSetVariable, n)
}
_ = env.Vars().Set(n, row[i])
}
return nil
}
// doExecExec executes a query and re-executes all columns of all rows as if they
// were their own queries.
func (h *Handler) doExecExec(ctx context.Context, w io.Writer, _ metacmd.Option, prefix, sqlstr string, qtyp bool, bind []interface{}) error {
// query
rows, err := h.DB().QueryContext(ctx, sqlstr, bind...)
if err != nil {
return err
}
// exec resulting rows
if err := h.doExecRows(ctx, w, rows); err != nil {
return err
}
// check for additional result sets ...
for rows.NextResultSet() {
if err := h.doExecRows(ctx, w, rows); err != nil {
return err
}
}
return nil
}
// doQuery executes a doQuery against the database.
func (h *Handler) doQuery(ctx context.Context, w io.Writer, opt metacmd.Option, typ, sqlstr string, bind []interface{}) error {
// run query
rows, err := h.DB().QueryContext(ctx, sqlstr, bind...)
if err != nil {
return err
}
defer rows.Close()
params := env.Vars().Print()
params["time"] = env.Vars().PrintTimeFormat()
for k, v := range opt.Params {
params[k] = v
}
var pipe io.WriteCloser
var cmd *exec.Cmd
if pipeName := params["pipe"]; pipeName != "" || h.out != nil {
if params["expanded"] == "auto" && params["columns"] == "" {
// don't rely on terminal size when piping output to a file or cmd
params["expanded"] = "off"
}
if pipeName != "" {
if pipeName[0] == '|' {
pipe, cmd, err = env.Pipe(h.l.Stdout(), h.l.Stderr(), pipeName[1:])
} else {
pipe, err = os.OpenFile(pipeName, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0o644)
}
if err != nil {
return err
}
w = pipe
}
} else if opt.Exec != metacmd.ExecWatch {
params["pager_cmd"] = env.Get("PAGER")
}
// set up column type config
var extra []tblfmt.Option
switch f := drivers.ColumnTypes(h.u); {
case f != nil:
extra = append(extra, tblfmt.WithColumnTypesFunc(f))
case drivers.UseColumnTypes(h.u):
extra = append(extra, tblfmt.WithUseColumnTypes(true))
}
resultSet := tblfmt.ResultSet(rows)
// wrap query with crosstab
if opt.Exec == metacmd.ExecCrosstab {
var err error
if resultSet, err = tblfmt.NewCrosstabView(rows, append(extra, tblfmt.WithParams(opt.Crosstab...))...); err != nil {
return err
}
extra = nil
}
if drivers.LowerColumnNames(h.u) {
params["lower_column_names"] = "true"
}
// encode and handle error conditions
switch err := tblfmt.EncodeAll(w, resultSet, params, extra...); {
case err != nil && cmd != nil && errors.Is(err, syscall.EPIPE):
// broken pipe means pager quit before consuming all data, which might be expected
return nil
case err != nil && h.u.Driver == "sqlserver" && err == tblfmt.ErrResultSetHasNoColumns && strings.HasPrefix(typ, "EXEC"):
// sqlserver EXEC statements sometimes do not have results, fake that
// it was executed as a exec and not a query
fmt.Fprintln(w, typ)
case err != nil:
return err
case params["format"] == "aligned":
fmt.Fprintln(w)
}
if pipe != nil {
pipe.Close()
if cmd != nil {
cmd.Wait()
}
}
return err
}
// doExecRows executes all the columns in the row.
func (h *Handler) doExecRows(ctx context.Context, w io.Writer, rows *sql.Rows) error {
// get columns
cols, err := drivers.Columns(h.u, rows)
if err != nil {
return err
}
// process rows
res := metacmd.Option{
Exec: metacmd.ExecOnly,
}
clen, tfmt := len(cols), env.Vars().PrintTimeFormat()
for rows.Next() {
if clen != 0 {
row, err := h.scan(rows, clen, tfmt)
if err != nil {
return err
}
// execute
for _, sqlstr := range row {
if err = h.Execute(ctx, w, res, stmt.FindPrefix(sqlstr, true, true, true), sqlstr, false); err != nil {
return err
}
}
}
}
return nil
}
// scan scans a row.
func (h *Handler) scan(rows *sql.Rows, clen int, tfmt string) ([]string, error) {
// scan to []interface{}
r := make([]interface{}, clen)
for i := range r {
r[i] = new(interface{})
}
if err := rows.Scan(r...); err != nil {
return nil, err
}
// get conversion funcs
cb, cm, cs, cd := drivers.ConvertBytes(h.u), drivers.ConvertMap(h.u), drivers.ConvertSlice(h.u), drivers.ConvertDefault(h.u)
row := make([]string, clen)
for n, z := range r {
j := z.(*interface{})
switch x := (*j).(type) {
case []byte:
if x != nil {
var err error
if row[n], err = cb(x, tfmt); err != nil {
return nil, err
}
}
case string:
row[n] = x
case time.Time:
row[n] = x.Format(tfmt)
case fmt.Stringer:
row[n] = x.String()
case map[string]interface{}:
if x != nil {
var err error
if row[n], err = cm(x); err != nil {
return nil, err
}
}
case []interface{}:
if x != nil {
var err error
if row[n], err = cs(x); err != nil {
return nil, err
}
}
default:
if x != nil {
var err error
if row[n], err = cd(x); err != nil {
return nil, err
}
}
}
}
return row, nil
}
// doExec does a database exec.
func (h *Handler) doExec(ctx context.Context, w io.Writer, _ metacmd.Option, typ, sqlstr string, bind []interface{}) error {
res, err := h.DB().ExecContext(ctx, sqlstr, bind...)
if err != nil {
_ = env.Vars().Set("ROW_COUNT", "0")
return err
}
// get affected
count, err := drivers.RowsAffected(h.u, res)
if err != nil {
_ = env.Vars().Set("ROW_COUNT", "0")
return err
}
// print name
if env.Get("QUIET") == "off" {
fmt.Fprint(w, typ)
// print count
if count > 0 {
fmt.Fprint(w, " ", count)
}
fmt.Fprintln(w)
}
return env.Vars().Set("ROW_COUNT", strconv.FormatInt(count, 10))
}
// Begin begins a transaction.
func (h *Handler) Begin(txOpts *sql.TxOptions) error {
return h.BeginTx(context.Background(), txOpts)
}
// Begin begins a transaction in a context.
func (h *Handler) BeginTx(ctx context.Context, txOpts *sql.TxOptions) error {
if h.db == nil {
return text.ErrNotConnected
}
if h.tx != nil {
return text.ErrPreviousTransactionExists
}
var err error
h.tx, err = h.db.BeginTx(ctx, txOpts)
if err != nil {
return drivers.WrapErr(h.u.Driver, err)
}
return nil
}
// Commit commits a transaction.
func (h *Handler) Commit() error {
if h.db == nil {
return text.ErrNotConnected
}
if h.tx == nil {
return text.ErrNoPreviousTransactionExists
}
tx := h.tx
h.tx = nil
if err := tx.Commit(); err != nil {
return drivers.WrapErr(h.u.Driver, err)
}
return nil
}
// Rollback rollbacks a transaction.
func (h *Handler) Rollback() error {
if h.db == nil {
return text.ErrNotConnected
}
if h.tx == nil {
return text.ErrNoPreviousTransactionExists
}
tx := h.tx
h.tx = nil
if err := tx.Rollback(); err != nil {
return drivers.WrapErr(h.u.Driver, err)
}
return nil
}
// If starts an if block.
func (h *Handler) If(ok bool) error {
return nil
}
// ElseIf starts an else if block.
func (h *Handler) ElseIf(ok bool) error {
return nil
}
// Else starts an else block.
func (h *Handler) Else(bool) error {
return nil
}
// EndIf closes an if block.
func (h *Handler) EndIf(bool) error {
return nil
}
// IncludeReader includes the content of rdr.
func (h *Handler) IncludeReader(rdr io.Reader, path string) error {
r := bufio.NewReader(rdr)
// setup rline
l := &rline.Rline{
N: func() ([]rune, error) {
buf := new(bytes.Buffer)
var b []byte
var isPrefix bool
var err error
for {
// read
b, isPrefix, err = r.ReadLine()
// when not EOF
if err != nil && err != io.EOF {
return nil, err
}
// append
if _, werr := buf.Write(b); werr != nil {
return nil, werr
}
// end of line
if !isPrefix || err != nil {
break
}
}
// peek and read possible line ending \n or \r\n
if err != io.EOF {
if err := peekEnding(buf, r); err != nil {
return nil, err
}
}
return []rune(buf.String()), err
},
Out: h.l.Stdout(),
Err: h.l.Stderr(),
Pw: h.l.Password,
}
p := New(l, h.user, filepath.Dir(path), h.charts, h.nopw)
p.db, p.u = h.db, h.u
drivers.ConfigStmt(p.u, p.buf)
err := p.Run()
h.db, h.u = p.db, p.u
return err
}
// Include includes the specified path.
func (h *Handler) Include(path string, relative bool) error {
if relative && !filepath.IsAbs(path) {
path = filepath.Join(h.wd, path)
}
// fmt.Fprintf(os.Stderr, "include: %s relative: %t\n", path, relative)
// open
path, f, err := env.OpenFile(h.user, path)
if err != nil {
return err
}
defer f.Close()
return h.IncludeReader(f, path)
}
// MetadataWriter loads the metadata writer for the
func (h *Handler) MetadataWriter(ctx context.Context) (metadata.Writer, error) {
if h.db == nil {
return nil, text.ErrNotConnected
}
return drivers.NewMetadataWriter(ctx, h.u, h.db, h.l.Stdout(), readerOpts()...)
}
// GetOutput gets the output writer.
func (h *Handler) GetOutput() io.Writer {
if h.out == nil {
return h.l.Stdout()
}
return h.out
}
// SetOutput sets the output writer.
func (h *Handler) SetOutput(o io.WriteCloser) {
if h.out != nil {
h.out.Close()
}
h.out = o
}
// FS is the filesystem interface.
type FS interface{}
// Error wraps handler errors.
type Error struct {
Buf string
Err error
}
// WrapErr wraps an [error] using the specified driver when err is not nil.
func WrapErr(buf string, err error) error {
if err == nil {
return nil
}
// avoid double wrapping error
if _, ok := err.(*Error); ok {
return err
}
return &Error{buf, err}
}
// Error satisfies the [error] interface, returning the original error message.
func (e *Error) Error() string {
return e.Err.Error()
}
// Unwrap returns the original error.
func (e *Error) Unwrap() error {
return e.Err
}
func readerOpts() []metadata.ReaderOption {
var opts []metadata.ReaderOption
if env.Get("ECHO_HIDDEN") == "on" || env.Get("ECHO_HIDDEN") == "noexec" {
if env.Get("ECHO_HIDDEN") == "noexec" {
opts = append(opts, metadata.WithDryRun(true))
}
opts = append(
opts,
metadata.WithLogger(log.New(os.Stdout, "DEBUG: ", log.LstdFlags)),
metadata.WithTimeout(30*time.Second),
)
}
return opts
}
// peekEnding peeks to see if the next successive bytes in r is \n or \r\n,
// writing to w if it is. Does not advance r if the next bytes are not \n or
// \r\n.
func peekEnding(w io.Writer, r *bufio.Reader) error {
// peek first byte
buf, err := r.Peek(1)
switch {
case err != nil && err != io.EOF:
return err
case err == nil && buf[0] == '\n':
if _, rerr := r.ReadByte(); err != nil && err != io.EOF {
return rerr
}
_, werr := w.Write([]byte{'\n'})
return werr
case err == nil && buf[0] != '\r':
return nil
}
// peek second byte
buf, err = r.Peek(1)
switch {
case err != nil && err != io.EOF:
return err
case err == nil && buf[0] != '\n':
return nil
}
if _, rerr := r.ReadByte(); err != nil && err != io.EOF {
return rerr
}
_, werr := w.Write([]byte{'\n'})
return werr
}
// grab returns the i'th rune from r when i < end, otherwise 0.
func grab(r []rune, i, end int) rune {
if i < end {
return r[i]
}
return 0
}
// empty reports whether s contains at least one printable, non-space
// character.
func empty(s string) bool {
return strings.IndexFunc(s, func(r rune) bool {
return unicode.IsPrint(r) && !unicode.IsSpace(r)
}) == -1
}
// lastcolor returns the last defined color in s, if any.
func lastcolor(s string) string {
if i := strings.LastIndex(s, "\n"); i != -1 {
s = s[:i]
}
if i := strings.LastIndex(s, "\x1b[0m"); i != -1 {
s = s[i+4:]
}
return strings.Join(ansiRE.FindAllString(s, -1), "")
}
// isSpaceOrControl returns true when r is a space or control character.
func isSpaceOrControl(r rune) bool {
return unicode.IsSpace(r) || unicode.IsControl(r)
}
// lastIndex returns the last index in r of needle, or -1 if not found.
func lastIndex(r []rune, needle rune) int {
for i := len(r) - 1; i >= 0; i-- {
if r[i] == needle {
return i
}
}
return -1
}
// ansiRE matches ansi escape (color) codes.
var ansiRE = regexp.MustCompile(`\x1b[[0-9]+([:;][0-9]+)*m`)
// lineendRE is the end of line terminal.
var lineendRE = regexp.MustCompile(`(?:\r?\n)+$`)
// helpQuitExitRE is a regexp to use to match help, quit, or exit messages.
var helpQuitExitRE = regexp.MustCompile(`(?im)^+(` + strings.Join([]string{text.HelpPrefix, text.QuitPrefix, text.ExitPrefix}, "|") + `)\s*$`)
================================================
FILE: internal/adodb.go
================================================
//go:build (all || most || adodb) && !no_adodb
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/adodb" // Microsoft ADODB driver
)
================================================
FILE: internal/athena.go
================================================
//go:build (all || most || athena) && !no_athena
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/athena" // AWS Athena driver
)
================================================
FILE: internal/avatica.go
================================================
//go:build (all || most || avatica) && !no_avatica
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/avatica" // Apache Avatica driver
)
================================================
FILE: internal/bigquery.go
================================================
//go:build (all || most || bigquery) && !no_bigquery
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/bigquery" // Google BigQuery driver
)
================================================
FILE: internal/cassandra.go
================================================
//go:build (all || most || cassandra) && !no_cassandra
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/cassandra" // Cassandra driver
)
================================================
FILE: internal/chai.go
================================================
//go:build (all || most || chai) && !no_chai
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/chai" // ChaiSQL driver
)
================================================
FILE: internal/clickhouse.go
================================================
//go:build (!no_base || clickhouse) && !no_clickhouse
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/clickhouse" // ClickHouse driver
)
================================================
FILE: internal/cosmos.go
================================================
//go:build (all || most || cosmos) && !no_cosmos
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/cosmos" // Azure CosmosDB driver
)
================================================
FILE: internal/couchbase.go
================================================
//go:build (all || most || couchbase) && !no_couchbase
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/couchbase" // Couchbase driver
)
================================================
FILE: internal/csvq.go
================================================
//go:build (!no_base || csvq) && !no_csvq
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/csvq" // CSVQ driver
)
================================================
FILE: internal/databend.go
================================================
//go:build (all || most || databend) && !no_databend
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/databend" // Databend driver
)
================================================
FILE: internal/databricks.go
================================================
//go:build (all || most || databricks) && !no_databricks
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/databricks" // Databricks driver
)
================================================
FILE: internal/duckdb.go
================================================
//go:build (all || most || duckdb) && !no_duckdb
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/duckdb" // DuckDB driver
)
================================================
FILE: internal/dynamodb.go
================================================
//go:build (all || most || dynamodb) && !no_dynamodb
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/dynamodb" // DynamoDb driver
)
================================================
FILE: internal/exasol.go
================================================
//go:build (all || most || exasol) && !no_exasol
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/exasol" // Exasol driver
)
================================================
FILE: internal/firebird.go
================================================
//go:build (all || most || firebird) && !no_firebird
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/firebird" // Firebird driver
)
================================================
FILE: internal/flightsql.go
================================================
//go:build (all || most || flightsql) && !no_flightsql
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/flightsql" // FlightSQL driver
)
================================================
FILE: internal/godror.go
================================================
//go:build (all || godror) && !no_godror
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/godror" // GO DRiver for ORacle driver
)
================================================
FILE: internal/h2.go
================================================
//go:build (all || most || h2) && !no_h2
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/h2" // Apache H2 driver
)
================================================
FILE: internal/hive.go
================================================
//go:build (all || most || hive) && !no_hive
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/hive" // Apache Hive driver
)
================================================
FILE: internal/ignite.go
================================================
//go:build (all || most || ignite) && !no_ignite
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/ignite" // Apache Ignite driver
)
================================================
FILE: internal/impala.go
================================================
//go:build (all || most || impala) && !no_impala
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/impala" // Apache Impala driver
)
================================================
FILE: internal/internal.go
================================================
// Package internal provides a way to obtain information about which database
// drivers were included at build.
package internal
// Code generated by gen.go. DO NOT EDIT.
// KnownBuildTags returns a map of known driver names to its respective build
// tags.
func KnownBuildTags() map[string]string {
return map[string]string{
"adodb": "adodb", // github.com/mattn/go-adodb
"athena": "awsathena", // github.com/uber/athenadriver/go
"avatica": "avatica", // github.com/apache/calcite-avatica-go/v5
"bigquery": "bigquery", // gorm.io/driver/bigquery/driver
"cassandra": "cql", // github.com/MichaelS11/go-cql-driver
"chai": "chai", // github.com/chaisql/chai
"clickhouse": "clickhouse", // github.com/ClickHouse/clickhouse-go/v2
"cosmos": "cosmos", // github.com/btnguyen2k/gocosmos
"couchbase": "n1ql", // github.com/couchbase/go_n1ql
"csvq": "csvq", // github.com/mithrandie/csvq-driver
"databend": "databend", // github.com/datafuselabs/databend-go
"databricks": "databricks", // github.com/databricks/databricks-sql-go
"duckdb": "duckdb", // github.com/duckdb/duckdb-go/v2
"dynamodb": "dynamodb", // github.com/btnguyen2k/godynamo
"exasol": "exasol", // github.com/exasol/exasol-driver-go
"firebird": "firebirdsql", // github.com/nakagami/firebirdsql
"flightsql": "flightsql", // github.com/apache/arrow/go/v17/arrow/flight/flightsql/driver
"godror": "godror", // github.com/godror/godror
"h2": "h2", // github.com/jmrobles/h2go
"hive": "hive", // sqlflow.org/gohive
"ignite": "ignite", // github.com/amsokol/ignite-go-client/sql
"impala": "impala", // github.com/sclgo/impala-go
"maxcompute": "maxcompute", // sqlflow.org/gomaxcompute
"moderncsqlite": "moderncsqlite", // modernc.org/sqlite
"mymysql": "mymysql", // github.com/ziutek/mymysql/godrv
"mysql": "mysql", // github.com/go-sql-driver/mysql
"netezza": "nzgo", // github.com/IBM/nzgo/v12
"odbc": "odbc", // github.com/alexbrainman/odbc
"oracle": "oracle", // github.com/sijms/go-ora/v2
"ots": "ots", // github.com/aliyun/aliyun-tablestore-go-sql-driver
"pgx": "pgx", // github.com/jackc/pgx/v5/stdlib
"postgres": "postgres", // github.com/lib/pq
"presto": "presto", // github.com/prestodb/presto-go-client/presto
"ql": "ql", // modernc.org/ql
"ramsql": "ramsql", // github.com/proullon/ramsql/driver
"sapase": "tds", // github.com/thda/tds
"saphana": "hdb", // github.com/SAP/go-hdb/driver
"snowflake": "snowflake", // github.com/snowflakedb/gosnowflake
"spanner": "spanner", // github.com/googleapis/go-sql-spanner
"sqlite3": "sqlite3", // github.com/mattn/go-sqlite3
"sqlserver": "sqlserver", // github.com/microsoft/go-mssqldb
"trino": "trino", // github.com/trinodb/trino-go-client/trino
"vertica": "vertica", // github.com/vertica/vertica-sql-go
"voltdb": "voltdb", // github.com/VoltDB/voltdb-client-go/voltdbclient
"ydb": "ydb", // github.com/ydb-platform/ydb-go-sdk/v3
}
}
================================================
FILE: internal/maxcompute.go
================================================
//go:build (all || most || maxcompute) && !no_maxcompute
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/maxcompute" // Alibaba MaxCompute driver
)
================================================
FILE: internal/moderncsqlite.go
================================================
//go:build (all || most || moderncsqlite) && !no_moderncsqlite
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/moderncsqlite" // ModernC SQLite3 driver
)
================================================
FILE: internal/mymysql.go
================================================
//go:build (all || most || mymysql) && !no_mymysql
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/mymysql" // MySQL MyMySQL driver
)
================================================
FILE: internal/mysql.go
================================================
//go:build (!no_base || mysql) && !no_mysql
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/mysql" // MySQL driver
)
================================================
FILE: internal/netezza.go
================================================
//go:build (all || most || netezza) && !no_netezza
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/netezza" // Netezza driver
)
================================================
FILE: internal/odbc.go
================================================
//go:build (all || odbc) && !no_odbc
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/odbc" // ODBC driver
)
================================================
FILE: internal/oracle.go
================================================
//go:build (!no_base || oracle) && !no_oracle
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/oracle" // Oracle Database driver
)
================================================
FILE: internal/ots.go
================================================
//go:build (all || most || ots) && !no_ots
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/ots" // Alibaba Tablestore driver
)
================================================
FILE: internal/pgx.go
================================================
//go:build (all || most || pgx) && !no_pgx
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/pgx" // PostgreSQL PGX driver
)
================================================
FILE: internal/postgres.go
================================================
//go:build (!no_base || postgres) && !no_postgres
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/postgres" // PostgreSQL driver
)
================================================
FILE: internal/presto.go
================================================
//go:build (all || most || presto) && !no_presto
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/presto" // Presto driver
)
================================================
FILE: internal/ql.go
================================================
//go:build (all || most || ql) && !no_ql
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/ql" // Cznic QL driver
)
================================================
FILE: internal/ramsql.go
================================================
//go:build (all || most || ramsql) && !no_ramsql
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/ramsql" // RamSQL driver
)
================================================
FILE: internal/sapase.go
================================================
//go:build (all || most || sapase) && !no_sapase
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/sapase" // SAP ASE driver
)
================================================
FILE: internal/saphana.go
================================================
//go:build (all || most || saphana) && !no_saphana
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/saphana" // SAP HANA driver
)
================================================
FILE: internal/snowflake.go
================================================
//go:build (all || most || snowflake) && !no_snowflake
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/snowflake" // Snowflake driver
)
================================================
FILE: internal/spanner.go
================================================
//go:build (all || most || spanner) && !no_spanner
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/spanner" // Google Spanner driver
)
================================================
FILE: internal/sqlite3.go
================================================
//go:build (!no_base || sqlite3) && !no_sqlite3
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/sqlite3" // SQLite3 driver
)
================================================
FILE: internal/sqlserver.go
================================================
//go:build (!no_base || sqlserver) && !no_sqlserver
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/sqlserver" // Microsoft SQL Server driver
)
================================================
FILE: internal/trino.go
================================================
//go:build (all || most || trino) && !no_trino
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/trino" // Trino driver
)
================================================
FILE: internal/vertica.go
================================================
//go:build (all || most || vertica) && !no_vertica
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/vertica" // Vertica driver
)
================================================
FILE: internal/voltdb.go
================================================
//go:build (all || most || voltdb) && !no_voltdb
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/voltdb" // VoltDB driver
)
================================================
FILE: internal/ydb.go
================================================
//go:build (all || most || ydb) && !no_ydb
package internal
// Code generated by gen.go. DO NOT EDIT.
import (
_ "github.com/xo/usql/drivers/ydb" // YDB driver
)
================================================
FILE: internal/z.go
================================================
package internal
import (
"runtime"
"github.com/xo/dburl"
"github.com/xo/usql/drivers"
)
func init() {
if runtime.GOOS == "windows" {
// if no odbc driver, but we have adodb, add 'odbc' (and related
// aliases) as alias for oleodbc
if drivers.Registered("adodb") && !drivers.Registered("odbc") {
old := dburl.Unregister("odbc")
dburl.RegisterAlias("oleodbc", "odbc")
for _, alias := range old.Aliases {
dburl.RegisterAlias("oleodbc", alias)
}
}
}
if drivers.Registered("moderncsqlite") && !drivers.Registered("sqlite3") {
old := dburl.Unregister("sqlite3")
dburl.RegisterAlias("moderncsqlite", "sqlite3")
for _, alias := range old.Aliases {
dburl.RegisterAlias("moderncsqlite", alias)
}
}
}
================================================
FILE: main.go
================================================
// Command usql is the universal command-line interface for SQL databases.
//
//go:debug x509negativeserial=1
package main
//go:generate go run gen.go
import (
"context"
"errors"
"fmt"
"io"
"os"
"strings"
"github.com/xo/usql/drivers"
"github.com/xo/usql/handler"
"github.com/xo/usql/internal"
"github.com/xo/usql/rline"
"github.com/xo/usql/text"
)
func main() {
// get available drivers and known build tags
available, known := drivers.Available(), internal.KnownBuildTags()
// report if database is supported
if len(os.Args) == 2 &&
strings.HasPrefix(os.Args[1], "--has-") &&
strings.HasSuffix(os.Args[1], "-support") {
n := os.Args[1][6 : len(os.Args[1])-8]
if v, ok := known[n]; ok {
n = v
}
var out int
if _, ok := available[n]; ok {
out = 1
}
fmt.Fprint(os.Stdout, out)
return
}
// run
if err := New(os.Args).ExecuteContext(context.Background()); err != nil && err != io.EOF && err != rline.ErrInterrupt {
var he *handler.Error
if !errors.As(err, &he) {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
}
var e *drivers.Error
if errors.As(err, &e) && e.Err == text.ErrDriverNotAvailable {
m := make(map[string]string, len(known))
for k, v := range known {
m[v] = k
}
tag := e.Driver
if t, ok := m[tag]; ok {
tag = t
}
rev := "latest"
if text.CommandVersion == "0.0.0-dev" || strings.Contains(text.CommandVersion, "-") {
rev = "master"
}
fmt.Fprintf(os.Stderr, text.GoInstallHint, tag, rev)
}
switch estr := err.Error(); {
case err == text.ErrWrongNumberOfArguments,
strings.HasPrefix(estr, "unknown flag:"),
strings.HasPrefix(estr, "unknown shorthand flag:"),
strings.HasPrefix(estr, "bad flag syntax:"),
strings.HasPrefix(estr, "flag needs an argument:"):
fmt.Fprintln(os.Stderr, text.CommandHelpHint)
}
os.Exit(1)
}
}
================================================
FILE: main_test.go
================================================
package main
import (
_ "github.com/google/goexpect"
)
================================================
FILE: metacmd/charts/charts.go
================================================
package charts
import (
"encoding/json"
"fmt"
"image/color"
"math"
"strconv"
"strings"
"github.com/kenshaw/colors"
"github.com/xo/usql/text"
)
type ChartConfig struct {
Title string
Subtitle string
W, H int
Background color.Color
Type string
Prec int
File string
}
func ParseArgs(opts map[string]string) (ChartConfig, error) {
cfg := ChartConfig{
Title: opts["title"],
Subtitle: opts["subtitle"],
W: 800,
H: 600,
Background: color.White,
Type: opts["type"],
}
if size, ok := opts["size"]; ok {
b, a, ok := strings.Cut(size, "x")
if !ok {
return ChartConfig{}, fmt.Errorf(text.ChartParseFailed, "size", "provide size as NxN")
}
var err error
cfg.W, err = strconv.Atoi(b)
if err != nil {
return ChartConfig{}, fmt.Errorf(text.ChartParseFailed, "size", err)
}
cfg.H, err = strconv.Atoi(a)
if err != nil {
return ChartConfig{}, fmt.Errorf(text.ChartParseFailed, "size", err)
}
}
if c, ok := opts["bg"]; ok {
var err error
cfg.Background, err = colors.Parse(c)
if err != nil {
return ChartConfig{}, fmt.Errorf(text.ChartParseFailed, "bg", err)
}
}
if prec, ok := opts["prec"]; ok {
p, err := strconv.Atoi(prec)
if err != nil {
return ChartConfig{}, fmt.Errorf(text.ChartParseFailed, "prec", err)
}
cfg.Prec = p
}
if file, ok := opts["file"]; ok {
cfg.File = file
}
return cfg, nil
}
type Chart struct {
Title string
Subtitle string
Legend []string
XAxis Series
YAxis Series
Series []Series
}
type Series struct {
Name string
Type string
Data any
}
func MakeChart(cfg ChartConfig, cols []string, transposed [][]string) (*Chart, error) {
numCols := make([][]float64, len(cols))
for i, col := range transposed {
for _, v := range col {
f, err := parseFloat(v, cfg.Prec)
if err != nil {
numCols[i] = nil
break
}
if numCols[i] == nil {
// don't allocate slice unless we have at least some valid data
numCols[i] = make([]float64, 0, len(col))
}
numCols[i] = append(numCols[i], f)
}
}
firstReg, firstNumeric := -1, -1
for i, c := range numCols {
if firstReg == -1 && c == nil {
firstReg = i
}
if firstNumeric == -1 && c != nil {
firstNumeric = i
}
}
c := &Chart{
Title: cfg.Title,
Subtitle: cfg.Subtitle,
}
var x int
var chartType string
switch {
case firstNumeric == -1:
return nil, text.ErrNoNumericColumns
case firstReg >= 0:
x = firstReg
chartType = "bar"
default:
x = firstNumeric
chartType = "line"
}
if cfg.Type != "" {
chartType = cfg.Type
}
c.XAxis = Series{
Name: cols[x],
Type: "category",
Data: transposed[x],
}
c.YAxis = Series{
Type: "value",
}
for i, col := range cols {
if i == x {
continue
}
c.Legend = append(c.Legend, col)
c.Series = append(c.Series, Series{
Name: col,
Type: chartType,
Data: numCols[i],
})
}
return c, nil
}
/* echarts */
type echarts struct {
Title *echartsTitle `json:"title,omitempty"`
Legend *echartsLegend `json:"legend,omitempty"`
XAxis *echartsAxis `json:"xAxis,omitempty"`
YAxis *echartsAxis `json:"yAxis,omitempty"`
Series []echartsAxis `json:"series,omitempty"`
}
type echartsTitle struct {
Title string `json:"text,omitempty"`
Subtext string `json:"subtext,omitempty"`
}
type echartsLegend struct {
Data []string `json:"data,omitempty"`
}
type echartsAxis struct {
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Data any `json:"data,omitempty"`
}
func (c Chart) ToEcharts() (string, error) {
ec := echarts{}
if c.Title != "" || c.Subtitle != "" {
ec.Title = &echartsTitle{c.Title, c.Subtitle}
}
if len(c.Legend) > 0 {
ec.Legend = &echartsLegend{c.Legend}
}
if c.XAxis.Data != nil || c.YAxis.Type != "" {
ec.XAxis = &echartsAxis{
Name: c.XAxis.Name,
Type: c.XAxis.Type,
Data: c.XAxis.Data,
}
}
if c.YAxis.Data != nil || c.YAxis.Type != "" {
ec.YAxis = &echartsAxis{
Name: c.YAxis.Name,
Type: c.YAxis.Type,
Data: c.YAxis.Data,
}
}
if len(c.Series) > 0 {
ec.Series = make([]echartsAxis, 0, len(c.Series))
for _, s := range c.Series {
ec.Series = append(ec.Series, echartsAxis{
Name: s.Name,
Type: s.Type,
Data: s.Data,
})
}
}
buf, err := json.Marshal(ec)
if err != nil {
return "", err
}
return string(buf), nil
}
func parseFloat(v string, prec int) (f float64, err error) {
f, err = strconv.ParseFloat(v, 64)
if err != nil || prec == 0 {
return
}
r := math.Pow(10, float64(prec))
return math.Round(f*r) / r, nil
}
const basicBarTemplate = `
{
"title": {
"text": {{ printf "%q" .Title }},
"subtext": {{ printf "%q" .Subtitle }}
},
{{- if .Legend }}
"legend": {
"data": [
{{ range .Legend }}{{ printf "%q" . }}{{ end }}
]
},
{{- end }}
"xAxis": [
{
"type": "category",
"data": [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
]
}
],
"yAxis": [
{
"type": "value"
}
],
"series": [
{
"name": "Rainfall",
"type": "bar",
"data": [
2,
4.9,
7,
23.2,
25.6,
76.7,
135.6,
162.2,
32.6,
20,
6.4,
3.3
],
},
{
"name": "Evaporation",
"type": "bar",
"data": [
2.6,
5.9,
9,
26.4,
28.7,
70.7,
175.6,
182.2,
48.7,
18.8,
6,
2.3
],
}
]
}
`
================================================
FILE: metacmd/cmds.go
================================================
package metacmd
import (
"bytes"
"context"
"database/sql"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"sort"
"strconv"
"strings"
"time"
"github.com/xo/dburl"
"github.com/xo/usql/drivers"
"github.com/xo/usql/env"
"github.com/xo/usql/text"
)
// Quit is a General meta command (\q \quit). Quits the application.
//
// Descs:
//
// q quit {{CommandName}}
// quit
func Quit(p *Params) error {
p.Option.Quit = true
return nil
}
// Copyright is a General meta command (\copyright). Writes the
// application's copyright message to the output.
//
// Descs:
//
// copyright show usage and distribution terms for {{CommandName}}
func Copyright(p *Params) error {
stdout := p.Handler.IO().Stdout()
if typ := env.TermGraphics(); typ.Available() {
typ.Encode(stdout, text.Logo)
}
fmt.Fprintln(stdout, text.Copyright)
return nil
}
// Drivers is a General meta command (\drivers). Writes information about the
// database drivers the application was built with to the output.
//
// Descs:
//
// drivers show database drivers available to {{CommandName}}
func Drivers(p *Params) error {
stdout, stderr := p.Handler.IO().Stdout(), p.Handler.IO().Stderr()
var cmd *exec.Cmd
var wc io.WriteCloser
if pager := env.Get("PAGER"); p.Handler.IO().Interactive() && pager != "" {
var err error
if wc, cmd, err = env.Pipe(stdout, stderr, pager); err != nil {
return err
}
stdout = wc
}
available := drivers.Available()
names := make([]string, len(available))
var z int
for k := range available {
names[z] = k
z++
}
sort.Strings(names)
fmt.Fprintln(stdout, text.AvailableDrivers)
for _, n := range names {
s := " " + n
driver, aliases := dburl.SchemeDriverAndAliases(n)
if driver != n {
s += " (" + driver + ")"
}
if len(aliases) > 0 {
s += " [" + strings.Join(aliases, ", ") + "]"
}
fmt.Fprintln(stdout, s)
}
if cmd != nil {
if err := wc.Close(); err != nil {
return err
}
return cmd.Wait()
}
return nil
}
// Help is a Help meta command (\?). Writes a help message to the output.
//
// Descs:
//
// ? [commands] show help on {{CommandName}}'s meta (backslash) commands
// ? options show help on {{CommandName}} command-line options
// ? variables show help on special {{CommandName}} variables
func Help(p *Params) error {
name, err := p.Next(false)
if err != nil {
return err
}
stdout, stderr := p.Handler.IO().Stdout(), p.Handler.IO().Stderr()
var cmd *exec.Cmd
var wc io.WriteCloser
if pager := env.Get("PAGER"); p.Handler.IO().Interactive() && pager != "" {
if wc, cmd, err = env.Pipe(stdout, stderr, pager); err != nil {
return err
}
stdout = wc
}
switch name = strings.TrimSpace(strings.ToLower(name)); {
case name == "options":
text.Usage(stdout, true)
case name == "variables":
_ = env.Listing(stdout)
default:
_ = Dump(stdout, name == "commands")
}
if cmd != nil {
if err := wc.Close(); err != nil {
return err
}
return cmd.Wait()
}
return nil
}
// Execute is a Query Execute meta command (\g and variants). Executes the
// active query on the open database connection.
//
// Descs:
//
// g [(OPTIONS)] [FILE] or ; execute query (and send results to file or |pipe)
// go:g
// G [(OPTIONS)] [FILE] as \g, but forces vertical output mode
// ego:G
// gx [(OPTIONS)] [FILE] as \g, but forces expanded output mode
// gexec execute query and execute each value of the result
// gset [PREFIX] execute query and store results in {{CommandName}} variables
func Execute(p *Params) error {
p.Option.Exec = ExecOnly
switch p.Name {
case "g", "go", "G", "ego", "gx", "gset":
params, err := p.All(true)
switch {
case err != nil:
return err
case p.Name != "gset":
p.Option.ParseParams(params, "pipe")
}
switch p.Name {
case "G", "ego":
p.Option.Params["format"] = "vertical"
case "gx":
p.Option.Params["expanded"] = "on"
case "gset":
p.Option.Exec = ExecSet
p.Option.ParseParams(params, "prefix")
}
case "gexec":
p.Option.Exec = ExecExec
}
return nil
}
// Bind is a Query Execute meta command (\bind). Sets (or unsets) variables to
// be used when executing a query.
//
// Descs:
//
// bind [PARAM]... set query parameters
func Bind(p *Params) error {
bind, err := p.All(true)
if err != nil {
return err
}
var v []interface{}
if n := len(bind); n != 0 {
v = make([]interface{}, len(bind))
for i := range n {
v[i] = bind[i]
}
}
p.Handler.Bind(v)
return nil
}
// Timing is a Query Execute meta command (\timing). Sets (or toggles) writing
// timing information for executed queries to the output.
//
// Descs:
//
// timing [on|off] toggle timing of commands
func Timing(p *Params) error {
v, err := p.Next(true)
switch {
case err != nil:
return err
case v == "":
p.Handler.SetTiming(!p.Handler.GetTiming())
default:
s, err := env.ParseBool(v, `\timing`)
if err != nil {
stderr := p.Handler.IO().Stderr()
fmt.Fprintf(stderr, "error: %v", err)
fmt.Fprintln(stderr)
}
var b bool
if s == "on" {
b = true
}
p.Handler.SetTiming(b)
}
setting := "off"
if p.Handler.GetTiming() {
setting = "on"
}
p.Handler.Print(text.TimingSet, setting)
return nil
}
// Crosstab is a Query View meta command (\crosstab). Executes the active query
// on the open database connection and displays results in a crosstab view.
//
// Descs:
//
// crosstab [(OPTIONS)] [COLUMNS] execute query and display results in crosstab
// crosstabview
// xtab
func Crosstab(p *Params) error {
p.Option.Exec = ExecCrosstab
for i := 0; i < 4; i++ {
col, ok, err := p.NextOK(true)
if err != nil {
return err
}
p.Option.Crosstab = append(p.Option.Crosstab, col)
if !ok {
break
}
}
return nil
}
// Chart is a Query View meta command (\chart). Executes the active query on
// the open database connection and displays results in a chart view.
//
// Descs:
//
// chart CHART [(OPTIONS)] execute query and display results as a chart
func Chart(p *Params) error {
p.Option.Exec = ExecChart
if p.Option.Params == nil {
p.Option.Params = make(map[string]string, 1)
}
params, err := p.All(true)
if err != nil {
return err
}
for i := 0; i < len(params); i++ {
param := params[i]
if param == "help" {
p.Option.Params["help"] = ""
return nil
}
equal := strings.IndexByte(param, '=')
switch {
case equal == -1 && i >= len(params)-1:
return text.ErrWrongNumberOfArguments
case equal == -1:
i++
p.Option.Params[param] = params[i]
default:
p.Option.Params[param[:equal]] = param[equal+1:]
}
}
return nil
}
// Watch is a Query View meta command (\watch). Executes (and re-executes) the
// active query on the open database connection until canceled by the user.
//
// Descs:
//
// watch [(OPTIONS)] [INTERVAL] execute query every specified interval
func Watch(p *Params) error {
p.Option.Exec = ExecWatch
p.Option.Watch = 2 * time.Second
switch s, ok, err := p.NextOK(true); {
case err != nil:
return err
case ok:
d, err := time.ParseDuration(s)
if err != nil {
if f, err := strconv.ParseFloat(s, 64); err == nil {
d = time.Duration(f * float64(time.Second))
}
}
if d == 0 {
return text.ErrInvalidWatchDuration
}
p.Option.Watch = d
}
return nil
}
// Connect is a Connection meta command (\c, \connect). Opens (connects) a
// database connection.
//
// Descs:
//
// c DSN or \c NAME connect to dsn or named database connection
// c DRIVER PARAMS... connect to database with driver and parameters
// connect
func Connect(p *Params) error {
vals, err := p.All(true)
if err != nil {
return err
}
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
return p.Handler.Open(ctx, vals...)
}
// Disconnect is a Connection meta command (\Z). Closes (disconnects) the
// current database connection.
//
// Descs:
//
// Z close (disconnect) database connection
// disconnect
func Disconnect(p *Params) error {
return p.Handler.Close()
}
// Password is a Connection meta command (\password). Changes the database
// user's password.
//
// Descs:
//
// password [USER] change password for user
// passwd
func Password(p *Params) error {
username, err := p.Next(true)
if err != nil {
return err
}
user, err := p.Handler.ChangePassword(username)
switch {
case err == text.ErrPasswordNotSupportedByDriver || err == text.ErrNotConnected:
return err
case err != nil:
return fmt.Errorf(text.PasswordChangeFailed, user, err)
}
// p.Handler.Print(text.PasswordChangeSucceeded, user)
return nil
}
// ConnectionInfo is a Connection meta command (\conninfo). Writes information
// about the connection to the output.
//
// Descs:
//
// conninfo display information about the current database connection
func ConnectionInfo(p *Params) error {
s := text.NotConnected
if db, u := p.Handler.DB(), p.Handler.URL(); db != nil && u != nil {
s = fmt.Sprintf(text.ConnInfo, u.Driver, u.DSN)
}
fmt.Fprintln(p.Handler.IO().Stdout(), s)
return nil
}
// Edit is a Query Buffer meta command (\e \edit). Opens the query buffer for
// editing in an external application.
//
// Descs:
//
// e [-raw|-exec] [FILE] [LINE] edit the query buffer, raw (non-interpolated) buffer, the exec buffer, or a file with external editor
// edit
func Edit(p *Params) error {
var exec bool
path, ok, err := p.NextOpt(true)
if ok {
if path != "exec" {
return fmt.Errorf(text.InvalidOption, path)
}
exec = true
if path, err = p.Next(true); err != nil {
return err
}
}
// get last statement
s, buf := "", p.Handler.Buf()
switch {
case buf.Len != 0 && exec:
s = buf.String()
case buf.Len != 0:
s = buf.RawString()
case exec:
s = p.Handler.LastExec()
default:
s = p.Handler.LastRaw()
}
line, err := p.Next(true)
if err != nil {
return err
}
// reset if no error
out, err := env.EditFile(p.Handler.User(), path, line, []byte(s))
if err != nil {
return err
}
// save edited buffer to history
p.Handler.IO().Save(string(out))
buf.Reset([]rune(string(out)))
return nil
}
// Print is a Query Buffer meta command (\p, \print, \raw, \exec). Writes the
// query buffer to the output.
//
// Descs:
//
// p [-raw|-exec] show the contents of the query buffer, the raw (non-interpolated) buffer or the exec buffer
// print
// raw
// exec
func Print(p *Params) error {
// get last statement
var s string
switch buf := p.Handler.Buf(); {
case buf.Len != 0 && p.Name == "exec":
s = buf.String()
case buf.Len != 0 && p.Name == "raw":
s = buf.RawString()
case buf.Len != 0:
s = buf.PrintString()
case p.Name == "exec":
s = p.Handler.LastExec()
case p.Name == "raw":
s = p.Handler.LastRaw()
default:
s = p.Handler.LastPrint()
}
switch {
case s == "":
s = text.QueryBufferEmpty
case p.Handler.IO().Interactive() && env.Get("SYNTAX_HL") == "true":
b := new(bytes.Buffer)
if p.Handler.Highlight(b, s) == nil {
s = b.String()
}
}
fmt.Fprintln(p.Handler.IO().Stdout(), s)
return nil
}
// Write is a Query Buffer meta command (\w \write). Writes the query buffer to
// a file.
//
// Descs:
//
// w [-raw|-exec] FILE write the contents of the query buffer, raw (non-interpolated) buffer, or exec buffer to file
// write
func Write(p *Params) error {
// get last statement
s, buf := p.Handler.LastExec(), p.Handler.Buf()
if buf.Len != 0 {
s = buf.String()
}
name, err := p.Next(true)
if err != nil {
return err
}
return os.WriteFile(name, []byte(strings.TrimSuffix(s, "\n")+"\n"), 0o644)
}
// Reset is a Query Buffer meta command (\r, \reset). Clears (resets) the query
// buffer.
//
// Descs:
//
// r reset (clear) the query buffer
// reset
func Reset(p *Params) error {
p.Handler.Reset(nil)
p.Handler.Print(text.QueryBufferReset)
return nil
}
// Echo is a Input/Output meta command (\echo, \warn, \qecho). Writes a message
// to the output.
//
// Descs:
//
// echo [-n] [MESSAGE]... write message to standard output (-n for no newline)
// qecho [-n] [MESSAGE]... write message to \o output stream (-n for no newline)
// warn [-n] [MESSAGE]... write message to standard error (-n for no newline)
func Echo(p *Params) error {
n, ok, err := p.NextOpt(true)
if err != nil {
return err
}
f := fmt.Fprintln
var vals []string
switch {
case ok && n == "n":
f = fmt.Fprint
case ok:
vals = append(vals, "-"+n)
default:
vals = append(vals, n)
}
v, err := p.All(true)
if err != nil {
return err
}
out := p.Handler.IO().Stdout()
switch o := p.Handler.GetOutput(); {
case p.Name == "qecho" && o != nil:
out = o
case p.Name == "warn":
out = p.Handler.IO().Stderr()
}
f(out, strings.Join(append(vals, v...), " "))
return nil
}
// Out is a Input/Output meta command (\o \out). Sets (redirects) the output to
// a file or a command.
//
// Descs:
//
// o [FILE] send all query results to file or |pipe
// out
func Out(p *Params) error {
p.Handler.SetOutput(nil)
params, err := p.All(true)
if err != nil {
return err
}
pipe := strings.Join(params, " ")
if pipe == "" {
return nil
}
var out io.WriteCloser
if pipe[0] == '|' {
out, _, err = env.Pipe(p.Handler.IO().Stdout(), p.Handler.IO().Stderr(), pipe[1:])
} else {
out, err = os.OpenFile(pipe, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0o644)
}
if err != nil {
return err
}
p.Handler.SetOutput(out)
return nil
}
// Copy is a Input/Output meta command (\copy). Copies data between databases.
//
// Descs:
//
// copy SRC DST QUERY TABLE copy results of query from source database into table on destination database
// copy SRC DST QUERY TABLE(A,...) copy results of query from source database into table's columns on destination database
func Copy(p *Params) error {
srcstr, err := p.Next(true)
if err != nil {
return err
}
src, err := dburl.Parse(srcstr)
if err != nil {
return err
}
deststr, err := p.Next(true)
if err != nil {
return err
}
dest, err := dburl.Parse(deststr)
if err != nil {
return err
}
query, err := p.Next(true)
if err != nil {
return err
}
table, err := p.Next(true)
if err != nil {
return err
}
ctx := context.Background()
stdout, stderr := p.Handler.IO().Stdout, p.Handler.IO().Stderr
srcDb, err := drivers.Open(ctx, src, stdout, stderr)
if err != nil {
return err
}
defer srcDb.Close()
destDb, err := drivers.Open(ctx, dest, stdout, stderr)
if err != nil {
return err
}
defer destDb.Close()
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
defer cancel()
// get the result set
r, err := srcDb.QueryContext(ctx, query)
if err != nil {
return err
}
defer r.Close()
n, err := drivers.Copy(ctx, dest, stdout, stderr, r, table)
if err != nil {
return err
}
p.Handler.Print("COPY %d", n)
return nil
}
// Include is a Control/Conditional meta command (\i, \include and variants).
// Includes (runs) the specified file in the current execution environment.
//
// Descs:
//
// i FILE execute commands from file
// include:i
// ir FILE as \i, but relative to location of current script
// include_relative:ir
func Include(p *Params) error {
path, err := p.Next(true)
if err != nil {
return err
}
relative := p.Name == "ir" || p.Name == "include_relative"
if err := p.Handler.Include(path, relative); err != nil {
return fmt.Errorf("%s: %v", path, err)
}
return nil
}
// Transact is a Transaction meta command (\begin, \commit, \rollback). Begins,
// commits, or aborts (rollback) the current database transaction on the open
// database connection.
//
// Descs:
//
// begin [-read-only [ISOLATION]] begin transaction, with optional isolation level
// commit commit current transaction
// rollback rollback (abort) current transaction
// abort:rollback
func Transact(p *Params) error {
switch p.Name {
case "commit":
return p.Handler.Commit()
case "rollback", "abort":
return p.Handler.Rollback()
}
// read begin params
readOnly := false
n, ok, err := p.NextOpt(true)
if ok {
if n != "read-only" {
return fmt.Errorf(text.InvalidOption, n)
}
readOnly = true
if n, err = p.Next(true); err != nil {
return err
}
}
// build tx options
var txOpts *sql.TxOptions
if readOnly || n != "" {
isolation := sql.LevelDefault
switch strings.ToLower(n) {
case "default", "":
case "read-uncommitted":
isolation = sql.LevelReadUncommitted
case "read-committed":
isolation = sql.LevelReadCommitted
case "write-committed":
isolation = sql.LevelWriteCommitted
case "repeatable-read":
isolation = sql.LevelRepeatableRead
case "snapshot":
isolation = sql.LevelSnapshot
case "serializable":
isolation = sql.LevelSerializable
case "linearizable":
isolation = sql.LevelLinearizable
default:
return text.ErrInvalidIsolationLevel
}
txOpts = &sql.TxOptions{
Isolation: isolation,
ReadOnly: readOnly,
}
}
// begin
return p.Handler.Begin(txOpts)
}
// Set is a Variables meta command (\set). Sets (or shows) the application variables.
//
// Descs:
//
// set [NAME [VALUE]] set {{CommandName}} application variable, or show all {{CommandName}} application variables if no parameters
func Set(p *Params) error {
switch n, ok, err := p.NextOK(true); {
case err != nil:
return err
case ok:
vals, err := p.All(true)
if err != nil {
return err
}
return env.Vars().Set(n, strings.Join(vals, " "))
}
return env.Vars().Dump(p.Handler.IO().Stdout())
}
// Unset is a Variables meta command (\unset). Unsets a application variable.
//
// Descs:
//
// unset NAME unset (delete) {{CommandName}} application variable
func Unset(p *Params) error {
n, err := p.Next(true)
if err != nil {
return err
}
return env.Vars().Unset(n)
}
// SetPrint is a Variables meta command (\pset, \a, \C, \f, \H, \t, \T, \x).
// Sets, toggles, or displays the application's print formatting variables.
//
// Descs:
//
// pset [NAME [VALUE]] set table print formatting option, or show all print formatting options if no parameters
// a toggle between unaligned and aligned output mode DEPRECATED
// C [TITLE] set table title, or unset if none DEPRECATED
// f [SEPARATOR] show or set field separator for unaligned query output DEPRECATED
// H toggle HTML output mode DEPRECATED
// T [ATTRIBUTES] set HTML tag attributes, or unset if none DEPRECATED
// t [on|off] show only rows DEPRECATED
// x [on|off|auto] toggle expanded output DEPRECATED
func SetPrint(p *Params) error {
var ok bool
var val string
var err error
switch p.Name {
case "a", "H":
default:
if val, ok, err = p.NextOK(true); err != nil {
return err
}
}
// display variables
if p.Name == "pset" && !ok {
return env.Vars().DumpPrint(p.Handler.IO().Stdout())
}
var field, extra string
switch p.Name {
case "pset":
field = val
if val, ok, err = p.NextOK(true); err != nil {
return err
}
case "a":
field = "format"
case "C":
field = "title"
case "f":
field = "fieldsep"
case "H":
field, extra = "format", "html"
case "t":
field = "tuples_only"
case "T":
field = "tableattr"
case "x":
field = "expanded"
}
if !ok {
if val, err = env.Vars().TogglePrint(field, extra); err != nil {
return err
}
} else {
if val, err = env.Vars().SetPrint(field, val); err != nil {
return err
}
}
// special replacement name for expanded field, when 'auto'
if field == "expanded" && val == "auto" {
field = "expanded_auto"
}
// format output
mask := text.FormatFieldNameSetMap[field]
unsetMask := text.FormatFieldNameUnsetMap[field]
switch {
case strings.Contains(mask, "%d"):
i, _ := strconv.Atoi(val)
p.Handler.Print(mask, i)
case unsetMask != "" && val == "":
p.Handler.Print(unsetMask)
case !strings.Contains(mask, "%"):
p.Handler.Print(mask)
default:
if field == "time" {
val = fmt.Sprintf("%q", val)
if tfmt := env.Vars().PrintTimeFormat(); tfmt != val {
val = fmt.Sprintf("%s (%q)", val, tfmt)
}
}
p.Handler.Print(mask, val)
}
return nil
}
// SetConn is a Variables meta command (\cset). Sets a connection variable.
//
// Descs:
//
// cset [NAME [URL]] set named connection, or show all named connections if no parameters
// cset NAME DRIVER PARAMS... set named connection for driver and parameters
func SetConn(p *Params) error {
switch n, ok, err := p.NextOK(true); {
case err != nil:
return err
case ok:
vals, err := p.All(true)
if err != nil {
return err
}
return env.Vars().SetConn(n, vals...)
}
return env.Vars().DumpConn(p.Handler.IO().Stdout())
}
// Prompt is a Variables meta command (\prompt). Prompts the user for input,
// setting a application variable to the user's response.
//
// Descs:
//
// prompt [-TYPE] VAR [PROMPT] prompt user to set application variable
func Prompt(p *Params) error {
typ := "string"
n, ok, err := p.NextOpt(true)
if err != nil {
return err
}
if ok {
typ = n
n, err = p.Next(true)
if err != nil {
return err
}
}
if n == "" {
return text.ErrMissingRequiredArgument
}
if err := env.ValidIdentifier(n); err != nil {
return err
}
vals, err := p.All(true)
if err != nil {
return err
}
v, err := p.Handler.ReadVar(typ, strings.Join(vals, " "))
if err != nil {
return err
}
return env.Vars().Set(n, v)
}
// Describe is a Informational meta command (\d and variants). Queries the open
// database connection for information about the database schema and writes the
// information to the output.
//
// Descs:
//
// d[S+] [NAME] list tables, views, and sequences or describe table, view, sequence, or index
// da[S+] [PATTERN] list aggregates
// df[S+] [PATTERN] list functions
// di[S+] [PATTERN] list indexes
// dm[S+] [PATTERN] list materialized views
// dn[S+] [PATTERN] list schemas
// dp[S] [PATTERN] list table, view, and sequence access privileges
// ds[S+] [PATTERN] list sequences
// dt[S+] [PATTERN] list tables
// dv[S+] [PATTERN] list views
// l[+] list databases
func Describe(p *Params) error {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
m, err := p.Handler.MetadataWriter(ctx)
if err != nil {
return err
}
verbose := strings.ContainsRune(p.Name, '+')
showSystem := strings.ContainsRune(p.Name, 'S')
name := strings.TrimRight(p.Name, "S+")
pattern, err := p.Next(true)
if err != nil {
return err
}
switch name {
case "d":
if pattern != "" {
return m.DescribeTableDetails(p.Handler.URL(), pattern, verbose, showSystem)
}
return m.ListTables(p.Handler.URL(), "tvmsE", pattern, verbose, showSystem)
case "df", "da":
return m.DescribeFunctions(p.Handler.URL(), name, pattern, verbose, showSystem)
case "dt", "dtv", "dtm", "dts", "dv", "dm", "ds":
return m.ListTables(p.Handler.URL(), name, pattern, verbose, showSystem)
case "dn":
return m.ListSchemas(p.Handler.URL(), pattern, verbose, showSystem)
case "di":
return m.ListIndexes(p.Handler.URL(), pattern, verbose, showSystem)
case "l":
return m.ListAllDbs(p.Handler.URL(), pattern, verbose)
case "dp":
return m.ListPrivilegeSummaries(p.Handler.URL(), pattern, showSystem)
}
return nil
}
// Stats is a Informational meta command (\ss and variants). Queries the open
// database connection for stats and writes it to the output.
//
// Descs:
//
// ss[+] [TABLE|QUERY] [k] show stats for a table or a query
func Stats(p *Params) error {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
m, err := p.Handler.MetadataWriter(ctx)
if err != nil {
return err
}
verbose := strings.ContainsRune(p.Name, '+')
name := strings.TrimRight(p.Name, "+")
pattern, err := p.Next(true)
if err != nil {
return err
}
k := 0
if verbose {
k = 3
}
if name == "ss" {
name = "sswnulhmkf"
}
val, ok, err := p.NextOK(true)
switch {
case err != nil:
return err
case ok:
verbose = true
if k, err = strconv.Atoi(val); err != nil {
return err
}
}
return m.ShowStats(p.Handler.URL(), name, pattern, verbose, k)
}
// Conditional is a Control/Conditional meta command (\if, \elif, \else,
// \endif). Starts, closes, and ends a conditional block within the
// application.
//
// Descs:
//
// if EXPR begin conditional block
// elif EXPR alternative within current conditional block
// else final alternative within current conditional block
// endif end conditional block
func Conditional(p *Params) error {
switch p.Name {
case "if":
case "elif":
case "else":
case "endif":
}
return nil
}
// Shell is a Operating System/Environment meta command (\!). Executes a
// command using the Operating System/Environment's shell.
//
// Descs:
//
// ! [COMMAND] execute command in shell or start interactive shell
func Shell(p *Params) error {
return env.Shell(p.Raw())
}
// Chdir is a Operating System/Environment meta command (\cd). Changes the
// current directory for the Operating System/Environment.
//
// Descs:
//
// cd [DIR] change the current working directory
func Chdir(p *Params) error {
dir, err := p.Next(true)
if err != nil {
return err
}
return env.Chdir(p.Handler.User(), dir)
}
// Getenv is a Operating System/Environment meta command (\getenv). Sets the
// application's variable value returned from the Operating
// System/Environment's variables.
//
// Descs:
//
// getenv VARNAME ENVVAR fetch environment variable
func Getenv(p *Params) error {
n, err := p.Next(true)
switch {
case err != nil:
return err
case n == "":
return text.ErrMissingRequiredArgument
}
v, err := p.Next(true)
switch {
case err != nil:
return err
case v == "":
return text.ErrMissingRequiredArgument
}
value, _ := env.Getenv(v)
return env.Vars().Set(n, value)
}
// Setenv is a Operating System/Environment meta command (\setenv). Sets (or
// unsets) a Operating System/Environment variable. Environment variables set
// this way will be passed to any child processes.
//
// Descs:
//
// setenv NAME [VALUE] set or unset environment variable
func Setenv(p *Params) error {
n, err := p.Next(true)
if err != nil {
return err
}
v, err := p.Next(true)
if err != nil {
return err
}
return os.Setenv(n, v)
}
================================================
FILE: metacmd/descs.go
================================================
package metacmd
// Code generated by gen.go. DO NOT EDIT.
import (
"github.com/xo/usql/text"
)
// sections are the command description sections.
var sections = []string{
"General",
"Help",
"Connection",
"Query Execute",
"Query View",
"Query Buffer",
"Informational",
"Variables",
"Input/Output",
"Control/Conditional",
"Transaction",
"Operating System/Environment",
}
// descs are the command descriptions.
var descs [][]desc
// cmds are the command lookup map.
var cmds map[string]func(*Params) error
func init() {
descs = [][]desc{
// General
{
{Quit, `q`, ``, `quit ` + text.CommandName + ``, false, false},
{Quit, `quit`, ``, `alias for \q`, true, false},
{Copyright, `copyright`, ``, `show usage and distribution terms for ` + text.CommandName + ``, false, false},
{Drivers, `drivers`, ``, `show database drivers available to ` + text.CommandName + ``, false, false},
},
// Help
{
{Help, `?`, `[commands]`, `show help on ` + text.CommandName + `'s meta (backslash) commands`, false, false},
{Help, `?`, `options`, `show help on ` + text.CommandName + ` command-line options`, false, false},
{Help, `?`, `variables`, `show help on special ` + text.CommandName + ` variables`, false, false},
},
// Connection
{
{Connect, `c`, `DSN or \c NAME`, `connect to dsn or named database connection`, false, false},
{Connect, `c`, `DRIVER PARAMS...`, `connect to database with driver and parameters`, false, false},
{Connect, `connect`, ``, `alias for \c`, true, false},
{Disconnect, `Z`, ``, `close (disconnect) database connection`, false, false},
{Disconnect, `disconnect`, ``, `alias for \Z`, true, false},
{Password, `password`, `[USER]`, `change password for user`, false, false},
{Password, `passwd`, ``, `alias for \password`, true, false},
{ConnectionInfo, `conninfo`, ``, `display information about the current database connection`, false, false},
},
// Query Execute
{
{Execute, `g`, `[(OPTIONS)] [FILE] or ;`, `execute query (and send results to file or |pipe)`, false, false},
{Execute, `go`, ``, `alias for \g`, true, false},
{Execute, `G`, `[(OPTIONS)] [FILE]`, `as \g, but forces vertical output mode`, false, false},
{Execute, `ego`, ``, `alias for \G`, true, false},
{Execute, `gx`, `[(OPTIONS)] [FILE]`, `as \g, but forces expanded output mode`, false, false},
{Execute, `gexec`, ``, `execute query and execute each value of the result`, false, false},
{Execute, `gset`, `[PREFIX]`, `execute query and store results in ` + text.CommandName + ` variables`, false, false},
{Bind, `bind`, `[PARAM]...`, `set query parameters`, false, false},
{Timing, `timing`, `[on|off]`, `toggle timing of commands`, false, false},
},
// Query View
{
{Crosstab, `crosstab`, `[(OPTIONS)] [COLUMNS]`, `execute query and display results in crosstab`, false, false},
{Crosstab, `crosstabview`, ``, `alias for \crosstab`, true, false},
{Crosstab, `xtab`, ``, `alias for \crosstab`, true, false},
{Chart, `chart`, `CHART [(OPTIONS)]`, `execute query and display results as a chart`, false, false},
{Watch, `watch`, `[(OPTIONS)] [INTERVAL]`, `execute query every specified interval`, false, false},
},
// Query Buffer
{
{Edit, `e`, `[-raw|-exec] [FILE] [LINE]`, `edit the query buffer, raw (non-interpolated) buffer, the exec buffer, or a file with external editor`, false, false},
{Edit, `edit`, ``, `alias for \e`, true, false},
{Print, `p`, `[-raw|-exec]`, `show the contents of the query buffer, the raw (non-interpolated) buffer or the exec buffer`, false, false},
{Print, `print`, ``, `alias for \p`, true, false},
{Print, `raw`, ``, `alias for \p`, true, false},
{Print, `exec`, ``, `alias for \p`, true, false},
{Write, `w`, `[-raw|-exec] FILE`, `write the contents of the query buffer, raw (non-interpolated) buffer, or exec buffer to file`, false, false},
{Write, `write`, ``, `alias for \w`, true, false},
{Reset, `r`, ``, `reset (clear) the query buffer`, false, false},
{Reset, `reset`, ``, `alias for \r`, true, false},
},
// Informational
{
{Describe, `d[S+]`, `[NAME]`, `list tables, views, and sequences or describe table, view, sequence, or index`, false, false},
{Describe, `da[S+]`, `[PATTERN]`, `list aggregates`, false, false},
{Describe, `df[S+]`, `[PATTERN]`, `list functions`, false, false},
{Describe, `di[S+]`, `[PATTERN]`, `list indexes`, false, false},
{Describe, `dm[S+]`, `[PATTERN]`, `list materialized views`, false, false},
{Describe, `dn[S+]`, `[PATTERN]`, `list schemas`, false, false},
{Describe, `dp[S]`, `[PATTERN]`, `list table, view, and sequence access privileges`, false, false},
{Describe, `ds[S+]`, `[PATTERN]`, `list sequences`, false, false},
{Describe, `dt[S+]`, `[PATTERN]`, `list tables`, false, false},
{Describe, `dv[S+]`, `[PATTERN]`, `list views`, false, false},
{Describe, `l[+]`, ``, `list databases`, false, false},
{Stats, `ss[+]`, `[TABLE|QUERY] [k]`, `show stats for a table or a query`, false, false},
},
// Variables
{
{Set, `set`, `[NAME [VALUE]]`, `set ` + text.CommandName + ` application variable, or show all ` + text.CommandName + ` application variables if no parameters`, false, false},
{Unset, `unset`, `NAME`, `unset (delete) ` + text.CommandName + ` application variable`, false, false},
{SetPrint, `pset`, `[NAME [VALUE]]`, `set table print formatting option, or show all print formatting options if no parameters`, false, false},
{SetPrint, `a`, ``, `toggle between unaligned and aligned output mode`, false, true},
{SetPrint, `C`, `[TITLE]`, `set table title, or unset if none`, false, true},
{SetPrint, `f`, `[SEPARATOR]`, `show or set field separator for unaligned query output`, false, true},
{SetPrint, `H`, ``, `toggle HTML output mode`, false, true},
{SetPrint, `T`, `[ATTRIBUTES]`, `set HTML tag attributes, or unset if none`, false, true},
{SetPrint, `t`, `[on|off]`, `show only rows`, false, true},
{SetPrint, `x`, `[on|off|auto]`, `toggle expanded output`, false, true},
{SetConn, `cset`, `[NAME [URL]]`, `set named connection, or show all named connections if no parameters`, false, false},
{SetConn, `cset`, `NAME DRIVER PARAMS...`, `set named connection for driver and parameters`, false, false},
{Prompt, `prompt`, `[-TYPE] VAR [PROMPT]`, `prompt user to set application variable`, false, false},
},
// Input/Output
{
{Echo, `echo`, `[-n] [MESSAGE]...`, `write message to standard output (-n for no newline)`, false, false},
{Echo, `qecho`, `[-n] [MESSAGE]...`, `write message to \o output stream (-n for no newline)`, false, false},
{Echo, `warn`, `[-n] [MESSAGE]...`, `write message to standard error (-n for no newline)`, false, false},
{Out, `o`, `[FILE]`, `send all query results to file or |pipe`, false, false},
{Out, `out`, ``, `alias for \o`, true, false},
{Copy, `copy`, `SRC DST QUERY TABLE`, `copy results of query from source database into table on destination database`, false, false},
{Copy, `copy`, `SRC DST QUERY TABLE(A,...)`, `copy results of query from source database into table's columns on destination database`, false, false},
},
// Control/Conditional
{
{Include, `i`, `FILE`, `execute commands from file`, false, false},
{Include, `include`, ``, `alias for \i`, true, false},
{Include, `ir`, `FILE`, `as \i, but relative to location of current script`, false, false},
{Include, `include_relative`, ``, `alias for \ir`, true, false},
{Conditional, `if`, `EXPR`, `begin conditional block`, false, false},
{Conditional, `elif`, `EXPR`, `alternative within current conditional block`, false, false},
{Conditional, `else`, ``, `final alternative within current conditional block`, false, false},
{Conditional, `endif`, ``, `end conditional block`, false, false},
},
// Transaction
{
{Transact, `begin`, `[-read-only [ISOLATION]]`, `begin transaction, with optional isolation level`, false, false},
{Transact, `commit`, ``, `commit current transaction`, false, false},
{Transact, `rollback`, ``, `rollback (abort) current transaction`, false, false},
{Transact, `abort`, ``, `alias for \rollback`, true, false},
},
// Operating System/Environment
{
{Shell, `!`, `[COMMAND]`, `execute command in shell or start interactive shell`, false, false},
{Chdir, `cd`, `[DIR]`, `change the current working directory`, false, false},
{Getenv, `getenv`, `VARNAME ENVVAR`, `fetch environment variable`, false, false},
{Setenv, `setenv`, `NAME [VALUE]`, `set or unset environment variable`, false, false},
},
}
cmds = make(map[string]func(*Params) error)
for i := range sections {
for _, desc := range descs[i] {
for _, n := range desc.Names() {
cmds[n] = desc.Func
}
}
}
}
================================================
FILE: metacmd/metacmd.go
================================================
// Package metacmd contains meta information and implementation for usql's
// backslash (\) commands.
package metacmd
import (
"context"
"database/sql"
"fmt"
"io"
"os/user"
"strings"
"time"
"github.com/mattn/go-runewidth"
"github.com/xo/dburl"
"github.com/xo/usql/drivers"
"github.com/xo/usql/drivers/metadata"
"github.com/xo/usql/env"
"github.com/xo/usql/rline"
"github.com/xo/usql/stmt"
"github.com/xo/usql/text"
)
// Handler is the shared interface for a command handler.
type Handler interface {
// IO handles the handler's IO.
IO() rline.IO
// User returns the current user.
User() *user.User
// URL returns the current database URL.
URL() *dburl.URL
// DB returns the current database connection.
DB() drivers.DB
// LastExec returns the last executed query.
LastExec() string
// LastPrint returns the last executed printable query.
LastPrint() string
// LastRaw returns the last raw (non-interpolated) query.
LastRaw() string
// Buf returns the current query buffer.
Buf() *stmt.Stmt
// Reset resets the last and current query buffer.
Reset([]rune)
// Bind binds query parameters.
Bind([]interface{})
// Open opens a database connection.
Open(context.Context, ...string) error
// Close closes the current database connection.
Close() error
// ChangePassword changes the password for a user.
ChangePassword(string) (string, error)
// ReadVar reads a variable of a specified type.
ReadVar(string, string) (string, error)
// Include includes a file.
Include(string, bool) error
// Begin begins a transaction.
Begin(*sql.TxOptions) error
// Commit commits the current transaction.
Commit() error
// Rollback aborts the current transaction.
Rollback() error
// Highlight highlights the statement.
Highlight(io.Writer, string) error
// GetTiming mode.
GetTiming() bool
// SetTiming mode.
SetTiming(bool)
// GetOutput writer.
GetOutput() io.Writer
// SetOutput writer.
SetOutput(io.WriteCloser)
// MetadataWriter retrieves the metadata writer for the handler.
MetadataWriter(context.Context) (metadata.Writer, error)
// Print formats according to a format specifier and writes to handler's standard output.
Print(string, ...interface{})
}
// Dump writes the command descriptions to w, separated by section.
func Dump(w io.Writer, hidden bool) error {
n := 0
for i := range sections {
for _, desc := range descs[i] {
if (!desc.Hidden && !desc.Deprecated) || hidden {
n = max(n, runewidth.StringWidth(desc.Name)+1+runewidth.StringWidth(desc.Params))
}
}
}
for i, s := range sections {
if i != 0 {
fmt.Fprintln(w)
}
fmt.Fprintln(w, s)
for _, desc := range descs[i] {
if (!desc.Hidden && !desc.Deprecated) || hidden {
_, _ = fmt.Fprintf(w, " \\%- *s %s\n", n, desc.Name+" "+desc.Params, wrap(desc.Desc, 95, n+5))
}
}
}
return nil
}
// Decode converts a command name (or alias) into a Runner.
func Decode(name string, params *stmt.Params) (func(Handler) (Option, error), error) {
f, ok := cmds[name]
if !ok || name == "" {
return nil, text.ErrUnknownCommand
}
return func(h Handler) (Option, error) {
p := &Params{
Handler: h,
Name: name,
Params: params,
}
err := f(p)
return p.Option, err
}, nil
}
// Params wraps metacmd parameters.
type Params struct {
// Handler is the process handler.
Handler Handler
// Name is the name of the metacmd.
Name string
// Params are the actual statement parameters.
Params *stmt.Params
// Option contains resulting command execution options.
Option Option
}
// Next returns the next command parameter, using env.Untick.
func (p *Params) Next(exec bool) (string, error) {
v, _, err := p.Params.Next(env.Untick(
p.Handler.User(),
env.Vars(),
exec,
))
if err != nil {
return "", err
}
return v, nil
}
// NextOK returns the next command parameter, using env.Untick.
func (p *Params) NextOK(exec bool) (string, bool, error) {
return p.Params.Next(env.Untick(
p.Handler.User(),
env.Vars(),
exec,
))
}
// NextOpt returns the next command parameter, using env.Untick. Returns true
// when the value is prefixed with a "-", along with the value sans the "-"
// prefix. Otherwise returns false and the value.
func (p *Params) NextOpt(exec bool) (string, bool, error) {
v, err := p.Next(exec)
switch {
case err != nil:
return "", false, err
case len(v) > 0 && v[0] == '-':
return v[1:], true, nil
}
return v, false, nil
}
// All gets all remaining command parameters using env.Untick.
func (p *Params) All(exec bool) ([]string, error) {
return p.Params.All(env.Untick(
p.Handler.User(),
env.Vars(),
exec,
))
}
// Raw returns the remaining command parameters as a raw string.
//
// Note: no other processing is done to interpolate variables or to decode
// string values.
func (p *Params) Raw() string {
return p.Params.Raw()
}
// Option contains parsed result options of a metacmd.
type Option struct {
// Quit instructs the handling code to quit.
Quit bool
// Exec informs the handling code of the type of execution.
Exec ExecType
// Params are accompanying string parameters for execution.
Params map[string]string
// Crosstab are the crosstab column parameters.
Crosstab []string
// Watch is the watch duration interval.
Watch time.Duration
}
func (opt *Option) ParseParams(params []string, defaultKey string) error {
if opt.Params == nil {
opt.Params = make(map[string]string, len(params))
}
formatOpts := false
for i, param := range params {
if len(param) == 0 {
continue
}
if !formatOpts {
if param[0] == '(' {
formatOpts = true
} else {
opt.Params[defaultKey] = strings.Join(params[i:], " ")
return nil
}
}
parts := strings.SplitN(param, "=", 2)
if len(parts) == 1 {
return text.ErrInvalidFormatOption
}
opt.Params[strings.TrimLeft(parts[0], "(")] = strings.TrimRight(parts[1], ")")
if formatOpts && param[len(param)-1] == ')' {
formatOpts = false
}
}
return nil
}
// ExecType represents the type of execution requested.
type ExecType int
const (
// ExecNone indicates no execution.
ExecNone ExecType = iota
// ExecOnly indicates plain execution only (\g).
ExecOnly
// ExecPipe indicates execution and piping results (\g |file)
ExecPipe
// ExecSet indicates execution and setting the resulting columns as
// variables (\gset).
ExecSet
// ExecExec indicates execution and executing the resulting rows (\gexec).
ExecExec
// ExecCrosstab indicates execution using crosstabview (\crosstabview).
ExecCrosstab
// ExecChart indicates execution using chart (\chart).
ExecChart
// ExecWatch indicates repeated execution with a fixed time interval.
ExecWatch
)
// desc wraps a meta command description.
type desc struct {
Func func(*Params) error
Name string
Params string
Desc string
Hidden bool
Deprecated bool
}
// Names returns the names for the command.
func (d desc) Names() []string {
switch i := strings.Index(d.Name, "["); {
case i == -1:
return []string{d.Name}
case !strings.HasSuffix(d.Name, "]"):
panic(fmt.Sprintf("invalid command %q", d.Name))
default:
name := d.Name[:i]
v := []string{name}
for _, s := range d.Name[i+1 : len(d.Name)-1] {
v = append(v, name+string(s))
}
return v
}
}
// wrap wraps a line of text to the specified width, and adding the prefix to
// each wrapped line.
func wrap(s string, width, prefixWidth int) string {
words := strings.Fields(strings.TrimSpace(s))
if len(words) == 0 {
return ""
}
prefix, wrapped := strings.Repeat(" ", prefixWidth), words[0]
left := width - prefixWidth - len(wrapped)
for _, word := range words[1:] {
if left < len(word)+1 {
wrapped += "\n" + prefix + word
left = width - len(word)
} else {
wrapped += " " + word
left -= 1 + len(word)
}
}
return wrapped
}
================================================
FILE: rline/rline.go
================================================
// Package rline provides a readline implementation for usql.
package rline
import (
"errors"
"io"
"os"
"github.com/gohxs/readline"
)
var (
// ErrInterrupt is the interrupt error.
ErrInterrupt = readline.ErrInterrupt
// ErrPasswordNotAvailable is the password not available error.
ErrPasswordNotAvailable = errors.New("password not available")
)
// IO is the common input/output interface.
type IO interface {
// Next returns the next line of runes (excluding '\n') from the input.
Next() ([]rune, error)
// Close closes the IO.
Close() error
// Stdout is the IO's standard out.
Stdout() io.Writer
// Stderr is the IO's standard error out.
Stderr() io.Writer
// Interactive determines if the IO is an interactive terminal.
Interactive() bool
// Cygwin determines if the IO is a Cygwin interactive terminal.
Cygwin() bool
// Prompt sets the prompt for the next interactive line read.
Prompt(string)
// Completer sets the auto-completer.
Completer(readline.AutoCompleter)
// Save saves a line of history.
Save(string) error
// Password prompts for a password.
Password(string) (string, error)
// SetOutput sets the output filter func.
SetOutput(func(string) string)
}
// Rline provides a type compatible with the IO interface.
type Rline struct {
Inst *readline.Instance
N func() ([]rune, error)
C func() error
Out io.Writer
Err io.Writer
Int bool
Cyg bool
P func(string)
A func(readline.AutoCompleter)
S func(string) error
Pw func(string) (string, error)
}
// Next returns the next line of runes (excluding '\n') from the input.
func (l *Rline) Next() ([]rune, error) {
if l.N != nil {
return l.N()
}
return nil, io.EOF
}
// Close closes the IO.
func (l *Rline) Close() error {
if l.C != nil {
return l.C()
}
return nil
}
// Stdout is the IO's standard out.
func (l *Rline) Stdout() io.Writer {
return l.Out
}
// Stderr is the IO's standard error out.
func (l *Rline) Stderr() io.Writer {
return l.Err
}
// Interactive determines if the IO is an interactive terminal.
func (l *Rline) Interactive() bool {
return l.Int
}
// Cygwin determines if the IO is a Cygwin interactive terminal.
func (l *Rline) Cygwin() bool {
return l.Cyg
}
// Prompt sets the prompt for the next interactive line read.
func (l *Rline) Prompt(s string) {
if l.P != nil {
l.P(s)
}
}
// Completer sets the auto-completer.
func (l *Rline) Completer(a readline.AutoCompleter) {
if l.A != nil {
l.A(a)
}
}
// Save saves a line of history.
func (l *Rline) Save(s string) error {
if l.S != nil {
return l.S(s)
}
return nil
}
// Password prompts for a password.
func (l *Rline) Password(prompt string) (string, error) {
if l.Pw != nil {
return l.Pw(prompt)
}
return "", ErrPasswordNotAvailable
}
// SetOutput sets the output format func.
func (l *Rline) SetOutput(f func(string) string) {
l.Inst.Config.Output = f
}
// New creates a new readline input/output handler.
func New(interactive, cygwin, forceNonInteractive bool, out, histfile string) (IO, error) {
var closers []func() error
// configure stdin
var stdin io.ReadCloser
switch {
case forceNonInteractive:
interactive, cygwin = false, false
case cygwin:
stdin = os.Stdin
default:
stdin = readline.Stdin
}
// configure stdout
var stdout io.WriteCloser
switch {
case out != "":
var err error
stdout, err = os.OpenFile(out, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return nil, err
}
closers = append(closers, stdout.Close)
interactive = false
case cygwin:
stdout = os.Stdout
default:
stdout = readline.Stdout
}
// configure stderr
var stderr io.Writer = os.Stderr
if !cygwin {
stderr = readline.Stderr
}
if interactive {
// wrap it with cancelable stdin
stdin = readline.NewCancelableStdin(stdin)
}
// create readline instance
l, err := readline.NewEx(&readline.Config{
HistoryFile: histfile,
DisableAutoSaveHistory: true,
InterruptPrompt: "^C",
HistorySearchFold: true,
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
FuncIsTerminal: func() bool {
return interactive || cygwin
},
FuncFilterInputRune: func(r rune) (rune, bool) {
if r == readline.CharCtrlZ {
return r, false
}
return r, true
},
})
if err != nil {
return nil, err
}
closers = append(closers, l.Close)
n := l.Operation.Runes
pw := func(prompt string) (string, error) {
buf, err := l.ReadPassword(prompt)
if err != nil {
return "", err
}
return string(buf), nil
}
if forceNonInteractive {
n, pw = nil, nil
}
return &Rline{
Inst: l,
N: n,
C: func() error {
for _, f := range closers {
_ = f()
}
return nil
},
Out: stdout,
Err: stderr,
Int: interactive || cygwin,
Cyg: cygwin,
P: l.SetPrompt,
A: func(a readline.AutoCompleter) {
cfg := l.Config.Clone()
cfg.AutoComplete = a
l.SetConfig(cfg)
},
S: l.SaveHistory,
Pw: pw,
}, nil
}
================================================
FILE: run.go
================================================
package main
import (
"context"
"errors"
"fmt"
"os"
"os/user"
"path/filepath"
"strings"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-billy/v5/osfs"
"github.com/mattn/go-isatty"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/xo/dburl"
"github.com/xo/usql/env"
"github.com/xo/usql/handler"
"github.com/xo/usql/rline"
"github.com/xo/usql/text"
)
// ContextExecutor is the command context.
type ContextExecutor interface {
ExecuteContext(context.Context) error
}
// New builds the command context.
func New(cliargs []string) ContextExecutor {
args := &Args{}
var (
bashCompletion bool
zshCompletion bool
fishCompletion bool
powershellCompletion bool
noDescriptions bool
badHelp bool
)
v := viper.New()
c := &cobra.Command{
Use: text.CommandName + " [flags]... [DSN]",
Short: text.Short(),
Version: text.CommandVersion,
SilenceErrors: true,
SilenceUsage: true,
DisableAutoGenTag: true,
DisableSuggestions: true,
Args: func(_ *cobra.Command, cliargs []string) error {
if len(cliargs) > 1 {
return text.ErrWrongNumberOfArguments
}
return nil
},
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
commandUpper := text.CommandUpper()
configFile := strings.TrimSpace(os.Getenv(commandUpper + "_CONFIG"))
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if s := strings.TrimSpace(f.Value.String()); f.Name == "config" && s != "" {
configFile = s
}
})
if configFile != "" {
v.SetConfigFile(configFile)
} else {
v.SetConfigName(text.ConfigName)
if configDir, err := os.UserConfigDir(); err == nil {
v.AddConfigPath(filepath.Join(configDir, text.CommandName))
}
}
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return err
}
}
v.SetEnvPrefix(commandUpper)
v.AutomaticEnv()
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if f.Name == "config" {
return
}
_ = v.BindEnv(f.Name, commandUpper+"_"+strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_")))
if !f.Changed && v.IsSet(f.Name) {
_ = cmd.Flags().Set(f.Name, fmt.Sprintf("%v", v.Get(f.Name)))
}
})
// unhide params
switch {
case bashCompletion,
zshCompletion,
fishCompletion,
powershellCompletion,
cmd.Name() == "__complete":
flags := cmd.Root().Flags()
for _, name := range []string{"no-psqlrc", "no-" + text.CommandName + "rc", "var", "variable"} {
flags.Lookup(name).Hidden = false
}
}
return nil
},
RunE: func(cmd *cobra.Command, cliargs []string) error {
// completions and short circuits
switch {
case bashCompletion:
return cmd.GenBashCompletionV2(os.Stdout, !noDescriptions)
case zshCompletion:
if noDescriptions {
return cmd.GenZshCompletionNoDesc(os.Stdout)
}
return cmd.GenZshCompletion(os.Stdout)
case fishCompletion:
return cmd.GenFishCompletion(os.Stdout, !noDescriptions)
case powershellCompletion:
if noDescriptions {
return cmd.GenPowerShellCompletion(os.Stdout)
}
return cmd.GenPowerShellCompletionWithDesc(os.Stdout)
case badHelp:
return errors.New("unknown shorthand flag: 'h' in -h")
}
// run
if len(cliargs) > 0 {
args.DSN = cliargs[0]
}
// create charts chroot
var err error
if args.Charts, err = chartsFS(v); err != nil {
return err
}
// fmt.Fprintf(os.Stderr, "\n\n%v\n\n", args.Charts)
args.Connections = v.GetStringMap("connections")
args.Init = v.GetString("init")
args.ConfigFileUsed = v.ConfigFileUsed()
return Run(cmd.Context(), args)
},
}
c.SetVersionTemplate("{{ .Name }} {{ .Version }}\n")
c.SetArgs(cliargs[1:])
c.SetUsageTemplate(text.UsageTemplate)
text.UsageString = c.UsageString
flags := c.Flags()
flags.SortFlags = false
// completions / short circuits
flags.BoolVar(&bashCompletion, "completion-script-bash", false, "output bash completion script and exit")
flags.BoolVar(&zshCompletion, "completion-script-zsh", false, "output zsh completion script and exit")
flags.BoolVar(&fishCompletion, "completion-script-fish", false, "output fish completion script and exit")
flags.BoolVar(&powershellCompletion, "completion-script-powershell", false, "output powershell completion script and exit")
flags.BoolVar(&noDescriptions, "no-descriptions", false, "disable descriptions in completion scripts")
flags.BoolVarP(&badHelp, "bad-help", "h", false, "bad help")
// command / file flags
flags.VarP(commandOrFile{args, true}, "command", "c", "run only single command (SQL or internal) and exit")
flags.VarP(commandOrFile{args, false}, "file", "f", "execute commands from file and exit")
// general flags
flags.BoolVarP(&args.NoPassword, "no-password", "w", false, "never prompt for password")
flags.BoolVarP(&args.NoInit, "no-init", "X", false, "do not execute initialization scripts (aliases: --no-rc --no-psqlrc --no-"+text.CommandName+"rc)")
flags.BoolVar(&args.NoInit, "no-rc", false, "do not read startup file")
flags.BoolVar(&args.NoInit, "no-psqlrc", false, "do not read startup file")
flags.BoolVar(&args.NoInit, "no-"+text.CommandName+"rc", false, "do not read startup file")
flags.VarP(filevar{&args.Out}, "out", "o", "output file")
flags.BoolVarP(&args.ForcePassword, "password", "W", false, "force password prompt (should happen automatically)")
flags.BoolVarP(&args.SingleTransaction, "single-transaction", "1", false, "execute as a single transaction (if non-interactive)")
// set
sf(flags, &args.Vars, "set", "v", `set variable NAME to VALUE (see \set command, aliases: --var --variable)`, "NAME=VALUE")
sf(flags, &args.Vars, "var", "", "set variable NAME to VALUE", "NAME=VALUE")
sf(flags, &args.Vars, "variable", "", "set variable NAME to VALUE", "NAME=VALUE")
// cset
sf(flags, &args.Cvars, "cset", "N", `set named connection NAME to DSN (see \cset command)`, "NAME=DSN")
// pset
sf(flags, &args.Pvars, "pset", "P", `set printing option VAR to ARG (see \pset command)`, "VAR=ARG")
// pset flags
sf(flags, &args.Pvars, "field-separator", "F", `field separator for unaligned and CSV output (default "|" and ",")`, "FIELD-SEPARATOR", "fieldsep=%q", "csv_fieldsep=%q")
sf(flags, &args.Pvars, "record-separator", "R", `record separator for unaligned and CSV output (default \n)`, "RECORD-SEPARATOR", "recordsep=%q")
sf(flags, &args.Pvars, "table-attr", "T", "set HTML table tag attributes (e.g., width, border)", "TABLE-ATTR", "tableattr=%q")
// pset bools
sf(flags, &args.Pvars, "no-align", "A", "unaligned table output mode", "", "format=unaligned")
sf(flags, &args.Pvars, "html", "H", "HTML table output mode", "", "format=html")
sf(flags, &args.Pvars, "tuples-only", "t", "print rows only", "", "tuples_only=on")
sf(flags, &args.Pvars, "expanded", "x", "turn on expanded table output", "", "expanded=on")
sf(flags, &args.Pvars, "field-separator-zero", "z", "set field separator for unaligned and CSV output to zero byte", "", "fieldsep_zero=on")
sf(flags, &args.Pvars, "record-separator-zero", "0", "set record separator for unaligned and CSV output to zero byte", "", "recordsep_zero=on")
sf(flags, &args.Pvars, "json", "J", "JSON output mode", "", "format=json")
sf(flags, &args.Pvars, "csv", "C", "CSV output mode", "", "format=csv")
sf(flags, &args.Pvars, "vertical", "G", "vertical output mode", "", "format=vertical")
// set bools
sf(flags, &args.Vars, "quiet", "q", "run quietly (no messages, only query output)", "", "QUIET=on")
// app config
_ = flags.StringP("config", "", "", "config file")
// manually set --version, see github.com/spf13/cobra/command.go
_ = flags.BoolP("version", "V", false, "output version information, then exit")
_ = flags.SetAnnotation("version", cobra.FlagSetByCobraAnnotation, []string{"true"})
// manually set --help, see github.com/spf13/cobra/command.go
_ = flags.BoolP("help", "?", false, "show this help, then exit")
_ = c.Flags().SetAnnotation("help", cobra.FlagSetByCobraAnnotation, []string{"true"})
// mark hidden
for _, name := range []string{
"no-rc", "no-psqlrc", "no-" + text.CommandName + "rc", "var", "variable",
"completion-script-bash", "completion-script-zsh", "completion-script-fish",
"completion-script-powershell", "no-descriptions",
"bad-help",
} {
flags.Lookup(name).Hidden = true
}
return c
}
// Run runs the application.
func Run(ctx context.Context, args *Args) error {
// get user
u, err := user.Current()
if err != nil {
return err
}
// get working directory
wd, err := os.Getwd()
if err != nil {
return err
}
// determine if interactive
interactive := isatty.IsTerminal(os.Stdout.Fd()) && isatty.IsTerminal(os.Stdin.Fd())
cygwin := isatty.IsCygwinTerminal(os.Stdout.Fd()) && isatty.IsCygwinTerminal(os.Stdin.Fd())
forceNonInteractive := len(args.CommandOrFiles) != 0
// enable term graphics
if !forceNonInteractive && interactive && !cygwin {
// NOTE: this is done here and not in the env.init() package, because
// NOTE: we need to determine if it is interactive first, otherwise it
// NOTE: could mess up the non-interactive output with control characters
var typ string
if s, _ := env.Getenv(text.CommandUpper()+"_TERM_GRAPHICS", "TERM_GRAPHICS"); s != "" {
typ = s
}
if err := env.Vars().Set("TERM_GRAPHICS", typ); err != nil {
return err
}
}
// configured named connections
for name, v := range args.Connections {
if err := setConn(name, v); err != nil && !forceNonInteractive && interactive {
fmt.Fprintln(os.Stderr, fmt.Sprintf(text.InvalidNamedConnection, name, err))
}
}
// fmt.Fprintf(os.Stdout, "VARS: %v\nCVARS: %v\nPVARS: %v\n", args.Vars, args.Cvars, args.Pvars)
// set vars
for _, v := range args.Vars {
if i := strings.Index(v, "="); i != -1 {
_ = env.Vars().Set(v[:i], v[i+1:])
} else {
_ = env.Vars().Unset(v)
}
}
// set cvars
for _, v := range args.Cvars {
if i := strings.Index(v, "="); i != -1 {
s := v[i+1:]
if c := s[0]; c == '\'' || c == '"' {
if s, err = env.Unquote(s); err != nil {
return err
}
}
if err = env.Vars().SetConn(v[:i], s); err != nil {
return err
}
} else {
if err = env.Vars().SetConn(v, ""); err != nil {
return err
}
}
}
// set pvars
for _, v := range args.Pvars {
if i := strings.Index(v, "="); i != -1 {
s := v[i+1:]
if c := s[0]; c == '\'' || c == '"' {
if s, err = env.Unquote(s); err != nil {
return err
}
}
if _, err = env.Vars().SetPrint(v[:i], s); err != nil {
return err
}
} else {
if _, err = env.Vars().TogglePrint(v, ""); err != nil {
return err
}
}
}
// create input/output
l, err := rline.New(interactive, cygwin, forceNonInteractive, args.Out, env.HistoryFile(u))
if err != nil {
return err
}
defer l.Close()
// create handler
h := handler.New(l, u, wd, args.Charts, args.NoPassword)
// force password
dsn := args.DSN
if args.ForcePassword {
if dsn, err = h.Password(dsn); err != nil {
return err
}
}
// open dsn
if err = h.Open(ctx, dsn); err != nil {
return err
}
// start transaction
if args.SingleTransaction {
if h.IO().Interactive() {
return text.ErrSingleTransactionCannotBeUsedWithInteractiveMode
}
if err = h.BeginTx(ctx, nil); err != nil {
return err
}
}
// init script
if !args.NoInit {
// rc file
if rc := env.RCFile(u); rc != "" {
if err = h.Include(rc, false); err != nil && err != text.ErrNoSuchFileOrDirectory {
return err
}
}
if args.Init != "" {
if err = h.IncludeReader(strings.NewReader(args.Init), args.ConfigFileUsed); err != nil {
return err
}
}
}
// setup runner
f := h.Run
if len(args.CommandOrFiles) != 0 {
f = runCommandOrFiles(h, args.CommandOrFiles)
}
// run
if err = f(); err != nil {
return err
}
// commit
if args.SingleTransaction {
return h.Commit()
}
return nil
}
// Args are the command line arguments.
type Args struct {
DSN string
CommandOrFiles []CommandOrFile
Out string
ForcePassword bool
NoPassword bool
NoInit bool
SingleTransaction bool
Vars []string
Cvars []string
Pvars []string
Charts billy.Filesystem
Connections map[string]interface{}
Init string
ConfigFileUsed string
}
// CommandOrFile is a special type to deal with interspersed -c, -f,
// command-line options, to ensure proper order execution.
type CommandOrFile struct {
Command bool
Value string
}
// commandOrFile provides a [pflag.Value] to wrap the command or file value in
// [Args].
type commandOrFile struct {
args *Args
command bool
}
// Set satisfies the [pflag.Value] interface.
func (c commandOrFile) Set(value string) error {
c.args.CommandOrFiles = append(c.args.CommandOrFiles, CommandOrFile{
Command: c.command,
Value: value,
})
return nil
}
// String satisfies the [pflag.Value] interface.
func (c commandOrFile) String() string {
return ""
}
// Type satisfies the [pflag.Value] interface.
func (c commandOrFile) Type() string {
if c.command {
return "COMMAND"
}
return "FILE"
}
// vs handles setting vars with predefined values.
type vs struct {
vars *[]string
vals []string
typ string
}
// Set satisfies the [pflag.Value] interface.
func (p vs) Set(value string) error {
if len(p.vals) != 0 {
for _, v := range p.vals {
if strings.Contains(v, "%") {
*p.vars = append(*p.vars, fmt.Sprintf(v, value))
} else {
*p.vars = append(*p.vars, v)
}
}
} else {
*p.vars = append(*p.vars, value)
}
return nil
}
// String satisfies the [pflag.Value] interface.
func (vs) String() string {
return ""
}
// Type satisfies the [pflag.Value] interface.
func (p vs) Type() string {
if p.typ == "" {
return "bool"
}
return p.typ
}
// filevar is a file var.
type filevar struct {
v *string
}
// Set satisfies the [pflag.Value] interface.
func (p filevar) Set(value string) error {
*p.v = value
return nil
}
// String satisfies the [pflag.Value] interface.
func (filevar) String() string {
return ""
}
// Type satisfies the [pflag.Value] interface.
func (filevar) Type() string {
return "FILE"
}
// chartsFS creates a filesystem for charts.
func chartsFS(v *viper.Viper) (billy.Filesystem, error) {
var configDir string
if s := v.ConfigFileUsed(); s != "" {
configDir = filepath.Dir(s)
} else {
var err error
if configDir, err = os.UserConfigDir(); err != nil {
return nil, err
}
configDir = filepath.Join(configDir, text.CommandName)
}
chartsPath := "charts"
if s := v.GetString("charts_path"); s != "" {
chartsPath = s
}
fs := osfs.New(configDir, osfs.WithBoundOS())
switch fi, err := fs.Stat(chartsPath); {
case err != nil && os.IsNotExist(err) && chartsPath == "charts":
return memfs.New(), nil
case err != nil && os.IsNotExist(err):
fmt.Fprintln(os.Stderr, fmt.Sprintf(text.ChartsPathDoesNotExist, chartsPath))
return memfs.New(), nil
case err != nil:
return nil, err
case !fi.IsDir():
fmt.Fprintln(os.Stderr, fmt.Sprintf(text.ChartsPathIsNotADirectory, chartsPath))
return memfs.New(), nil
}
return fs.Chroot(chartsPath)
}
// setConn sets a connection name to a DSN built from the passed value.
func setConn(name string, value interface{}) error {
switch x := value.(type) {
case string:
return env.Vars().SetConn(name, x)
case []interface{}:
return env.Vars().SetConn(name, convSlice(x)...)
case map[string]interface{}:
urlstr, err := dburl.BuildURL(x)
if err != nil {
return err
}
return env.Vars().SetConn(name, urlstr)
}
return text.ErrInvalidConfig
}
// runCommandOrFiles processes all the supplied commands or files.
func runCommandOrFiles(h *handler.Handler, commandsOrFiles []CommandOrFile) func() error {
return func() error {
for _, c := range commandsOrFiles {
h.SetSingleLineMode(c.Command)
if c.Command {
h.Reset([]rune(c.Value))
if err := h.Run(); err != nil {
return err
}
} else {
if err := h.Include(c.Value, false); err != nil {
return err
}
}
}
return nil
}
}
// sf sets a flag.
func sf(flags *pflag.FlagSet, v *[]string, name, short, usage, placeholder string, vals ...string) {
f := flags.VarPF(vs{v, vals, placeholder}, name, short, usage)
if placeholder == "" {
f.DefValue, f.NoOptDefVal = "true", "true"
}
}
// convSlice converts a generic slice to a string slice.
func convSlice(v []interface{}) []string {
s := make([]string, len(v))
for i, x := range v {
s[i] = fmt.Sprintf("%s", x)
}
return s
}
================================================
FILE: stmt/params.go
================================================
package stmt
import (
"unicode"
"github.com/xo/usql/text"
)
// Params holds information about command parameters.
type Params struct {
R []rune
Len int
}
// NewParams creates command parameters.
func NewParams(params string) *Params {
r := []rune(params)
return &Params{
R: r,
Len: len(r),
}
}
// Raw reads all remaining runes. No substitution or whitespace removal is
// performed.
func (p *Params) Raw() string {
s := string(p.R)
p.R, p.Len = p.R[:0], 0
return s
}
// Next reads the next command parameter using the provided substitution func.
// True indicates there are runes remaining in the command parameters to
// process.
func (p *Params) Next(unquote func(string, bool) (string, bool, error)) (string, bool, error) {
i, _ := findNonSpace(p.R, 0, p.Len)
if i >= p.Len {
return "", false, nil
}
var ok bool
var quote rune
start := i
loop:
for ; i < p.Len; i++ {
c, next := p.R[i], grab(p.R, i+1, p.Len)
switch {
case quote != 0:
start := i - 1
i, ok = readString(p.R, i, p.Len, quote, "")
if !ok {
break loop
}
switch z, ok, err := unquote(string(p.R[start:i+1]), false); {
case err != nil:
return "", false, err
case ok:
p.R, p.Len = substitute(p.R, start, p.Len, i-start+1, z)
i = start + len([]rune(z)) - 1
}
quote = 0
// start of single, double, or backtick string
case c == '\'' || c == '"' || c == '`':
quote = c
case c == ':' && next != ':':
if v := readVar(p.R, i, p.Len, next); v != nil {
switch z, ok, err := unquote(v.Name, true); {
case err != nil:
return "", false, err
case ok || v.Quote == '?':
p.R, p.Len = v.Substitute(p.R, z, ok)
i += v.Len - 1
default:
i = v.End - 1
}
}
case unicode.IsSpace(c):
break loop
}
}
if quote != 0 {
return "", false, text.ErrUnterminatedQuotedString
}
s := string(p.R[start:i])
p.R = p.R[i:]
p.Len = len(p.R)
return s, true, nil
}
// All retrieves all remaining command parameters using the provided
// substitution func. Will return on the first encountered error.
func (p *Params) All(unquote func(string, bool) (string, bool, error)) ([]string, error) {
var v []string
loop:
for {
switch s, ok, err := p.Next(unquote); {
case err != nil:
return v, err
case !ok:
break loop
default:
v = append(v, s)
}
}
return v, nil
}
// Arg retrieves the next argument, without decoding.
func (p *Params) Arg() (string, bool, error) {
return p.Next(func(s string, _ bool) (string, bool, error) {
return s, true, nil
})
}
================================================
FILE: stmt/params_test.go
================================================
package stmt
import (
"os/user"
"reflect"
"strconv"
"testing"
"github.com/xo/usql/env"
"github.com/xo/usql/text"
)
func TestParamsGetRaw(t *testing.T) {
const exp = ` 'a string' "another string" `
p := NewParams(exp)
s := p.Raw()
if s != exp {
t.Errorf("expected %q, got: %q", exp, s)
}
u, err := user.Current()
if err != nil {
t.Fatalf("expected no error, got: %v", err)
}
unquote := testUnquote(t, u)
switch s, ok, err := p.Next(unquote); {
case err != nil:
t.Fatalf("expected no error, got: %v", err)
case s != "":
t.Errorf("expected empty string, got: %q", s)
case ok:
t.Errorf("expected ok=false, got: %t", ok)
}
switch v, err := p.All(unquote); {
case err != nil:
t.Fatalf("expected no error, got: %v", err)
case len(v) != 0:
t.Errorf("expected v to have length 0, got: %d", len(v))
}
}
func TestParamsGetAll(t *testing.T) {
u, err := user.Current()
if err != nil {
t.Fatalf("expected no error, got: %v", err)
}
tests := []struct {
s string
exp []string
err error
}{
{``, nil, nil},
{` `, nil, nil},
{` :foo`, []string{`bar`}, nil},
{` :'foo`, nil, text.ErrUnterminatedQuotedString},
{` :'型示師`, nil, text.ErrUnterminatedQuotedString},
{` :"型示師`, nil, text.ErrUnterminatedQuotedString},
{` :'型示師 `, nil, text.ErrUnterminatedQuotedString},
{` :"型示師 `, nil, text.ErrUnterminatedQuotedString},
{`:'foo'`, []string{`'bar'`}, nil},
{` :'foo' `, []string{`'bar'`}, nil},
{`:'foo':foo`, []string{`'bar'bar`}, nil},
{`:'foo':foo:"foo"`, []string{`'bar'bar"bar"`}, nil},
{`:'foo':foo:foo`, []string{`'bar'barbar`}, nil},
{` :'foo':foo:foo`, []string{`'bar'barbar`}, nil},
{` :'foo':yes:foo`, []string{`'bar':yesbar`}, nil},
{` :foo `, []string{`bar`}, nil},
{`:foo:foo`, []string{`barbar`}, nil},
{` :foo:foo `, []string{`barbar`}, nil},
{` :foo:foo `, []string{`barbar`}, nil},
{`'hello'`, []string{`hello`}, nil},
{` 'hello''yes' `, []string{`hello'yes`}, nil},
{` 'hello\'...\'yes' `, []string{`hello'...'yes`}, nil},
{` "hello\'...\'yes" `, nil, text.ErrInvalidQuotedString},
{` "hello\"...\"yes" `, nil, text.ErrInvalidQuotedString},
{` 'hello':'yes' `, []string{`hello:'yes'`}, nil},
{` :'foo `, nil, text.ErrUnterminatedQuotedString},
{` :'foo bar`, nil, text.ErrUnterminatedQuotedString},
{` :'foo bar`, nil, text.ErrUnterminatedQuotedString},
{` :'foo bar `, nil, text.ErrUnterminatedQuotedString},
{" `foo", nil, text.ErrUnterminatedQuotedString},
{" `foo bar`", []string{"foo bar"}, nil},
{" `foo :foo`", []string{"foo :foo"}, nil},
{` :'foo':"foo"`, []string{`'bar'"bar"`}, nil},
{` :'foo' :"foo" `, []string{`'bar'`, `"bar"`}, nil},
{` :'foo' :"foo"`, []string{`'bar'`, `"bar"`}, nil},
{` :'foo' :"foo"`, []string{`'bar'`, `"bar"`}, nil},
{` :'foo' :"foo" `, []string{`'bar'`, `"bar"`}, nil},
{` :'foo' :"foo" :foo `, []string{`'bar'`, `"bar"`, `bar`}, nil},
{` :'foo':foo:"foo" `, []string{`'bar'bar"bar"`}, nil},
{` :'foo''yes':'foo' `, []string{`'bar'yes'bar'`}, nil},
{` :'foo' 'yes' :'foo' `, []string{`'bar'`, `yes`, `'bar'`}, nil},
{` 'yes':'foo':"foo"'blah''no' "\ntest" `, []string{`yes'bar'"bar"blah'no`, "\ntest"}, nil},
{`:型示師:'型示師':"型示師"`, []string{`:型示師:'型示師':"型示師"`}, nil},
{`:型示師 :'型示師' :"型示師"`, []string{`:型示師`, `:'型示師'`, `:"型示師"`}, nil},
{` :型示師 :'型示師' :"型示師" `, []string{`:型示師`, `:'型示師'`, `:"型示師"`}, nil},
{` :{?foo} `, []string{`TRUE`}, nil},
{` :{?foo_} `, []string{`FALSE`}, nil},
{` :{?型示} `, []string{`TRUE`}, nil},
{` :{?型示師} `, []string{`FALSE`}, nil},
{` :{?型示師 } `, []string{`:{?型示師`, `}`}, nil},
{` :{?foo }`, []string{`:{?foo`, `}`}, nil},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
vals, err := NewParams(test.s).All(testUnquote(t, u))
if err != test.err {
t.Fatalf("expected error %v, got: %v", test.err, err)
}
if !reflect.DeepEqual(vals, test.exp) {
t.Errorf("expected %v, got: %v", test.exp, vals)
}
})
}
}
func testUnquote(t *testing.T, u *user.User) func(string, bool) (string, bool, error) {
t.Helper()
vars := env.NewVars()
vars.Set("foo", "bar")
vars.Set("型示", "yes")
f := env.Untick(u, vars, false)
return func(s string, isvar bool) (string, bool, error) {
// t.Logf("test %d %q s: %q, isvar: %t", i, teststr, s, isvar)
return f(s, isvar)
}
}
================================================
FILE: stmt/parse.go
================================================
package stmt
import (
"regexp"
"unicode"
)
// prefixCount is the number of words to extract from a prefix.
const prefixCount = 6
// maxVarNameLen is the maximum var name length.
const maxVarNameLen = 128
// grab returns the i'th rune from r when i < end, otherwise 0.
func grab(r []rune, i, end int) rune {
if i < end {
return r[i]
}
return 0
}
// findSpace finds first space rune in r, returning end if not found.
func findSpace(r []rune, i, end int) (int, bool) {
for ; i < end; i++ {
if isSpaceOrControl(r[i]) {
return i, true
}
}
return i, false
}
// findNonSpace finds first non space rune in r, returning end if not found.
func findNonSpace(r []rune, i, end int) (int, bool) {
for ; i < end; i++ {
if !isSpaceOrControl(r[i]) {
return i, true
}
}
return i, false
}
// findRune finds the next rune c in r, returning end if not found.
func findRune(r []rune, i, end int, c rune) (int, bool) {
for ; i < end; i++ {
if r[i] == c {
return i, true
}
}
return i, false
}
// isEmptyLine returns true when r is empty or composed of only whitespace.
func isEmptyLine(r []rune, i, end int) bool {
_, ok := findNonSpace(r, i, end)
return !ok
}
// identifierRE is a regexp that matches dollar tag identifiers ($tag$).
var identifierRE = regexp.MustCompile(`(?i)^[a-z_][a-z0-9_]{0,127}$`)
// readDollarAndTag reads a dollar style $tag$ in r, starting at i, returning
// the enclosed "tag" and position, or -1 if the dollar and tag was invalid.
func readDollarAndTag(r []rune, i, end int) (string, int, bool) {
start, found := i, false
i++
for ; i < end; i++ {
if r[i] == '$' {
found = true
break
}
if i-start > 128 {
break
}
}
if !found {
return "", i, false
}
// check valid identifier
id := string(r[start+1 : i])
if id != "" && !identifierRE.MatchString(id) {
return "", i, false
}
return id, i, true
}
// readString seeks to the end of a string returning the position and whether
// or not the string's end was found.
//
// If the string's terminator was not found, then the result will be the passed
// end.
func readString(r []rune, i, end int, quote rune, tag string) (int, bool) {
var prev, c, next rune
for ; i < end; i++ {
c, next = r[i], grab(r, i+1, end)
switch {
case quote == '\'' && c == '\\':
i++
prev = 0
continue
case quote == '\'' && c == '\'' && next == '\'':
i++
continue
case quote == '\'' && c == '\'' && prev != '\'',
quote == '"' && c == '"',
quote == '`' && c == '`':
return i, true
case quote == '$' && c == '$':
if id, pos, ok := readDollarAndTag(r, i, end); ok && tag == id {
return pos, true
}
}
prev = c
}
return end, false
}
// readMultilineComment finds the end of a multiline comment (ie, '*/').
func readMultilineComment(r []rune, i, end int) (int, bool) {
i++
for ; i < end; i++ {
if r[i-1] == '*' && r[i] == '/' {
return i, true
}
}
return end, false
}
// readVar reads variable from r in the form of :var_name :'var_name'
// :"var_name" and :{?var_name}.
func readVar(r []rune, i, end int, next rune) *Var {
o := 1
switch next {
case '\'', '"':
o = 2
case '{':
if grab(r, i+2, end) != '?' {
return nil
}
o, next = 3, '}'
default:
next = 0
}
n, q := readVarName(r, i+o, end, next)
v := n
switch {
case n-i < o+1, next != 0 && next != q:
return nil
case next != 0:
v++
}
if next == '}' {
next = '?'
}
return &Var{
I: i,
End: v,
Quote: next,
Name: string(r[i+o : n]),
}
}
// readVarName reads a variable name up to maxVarNameLen.
func readVarName(r []rune, i, end int, q rune) (int, rune) {
c := rune(-1)
for n := 0; i < end && n < maxVarNameLen && c != q; i, n = i+1, n+1 {
switch c = grab(r, i, end); {
case c == 0, c == q, c != '_' && !unicode.IsLetter(c) && !unicode.IsNumber(c):
return i, c
}
}
return i, 0
}
// readCommand reads the command and any parameters from r, returning the
// offset from i for the end of command, and the end of the command parameters.
//
// A command is defined as the first non-blank text after \, followed by
// parameters up to either the next \ or a control character (for example, \n):
func readCommand(r []rune, i, end int) (int, int) {
// find end of command
c, next := rune(0), rune(0)
command:
for ; i < end; i++ {
switch next = grab(r, i+1, end); {
case next == 0:
return end, end
case next == '\\' || unicode.IsControl(next):
i++
return i, i
case unicode.IsSpace(next):
i++
break command
}
}
cmd, quote := i, rune(0)
params:
// find end of params
for ; i < end; i++ {
switch c, next = r[i], grab(r, i+1, end); {
case next == 0:
return cmd, end
case quote == 0 && (c == '\'' || c == '"' || c == '`'):
quote = c
case quote != 0 && c == quote:
quote = 0
// skip escaped
case quote != 0 && c == '\\' && (next == quote || next == '\\'):
i++
case quote == 0 && (c == '\\' || unicode.IsControl(c)):
break params
}
}
// log.Printf(">>> params: %q remaining: %q", string(r[cmd:i]), string(r[i:end]))
return cmd, i
}
// findPrefix finds the prefix in r up to n words.
func findPrefix(r []rune, n int, allowCComments, allowHashComments, allowMultilineComments bool) string {
var s []rune
var words int
loop:
for i, end := 0, len(r); i < end; i++ {
// skip space + control characters
if j, _ := findNonSpace(r, i, end); i != j {
r, end, i = r[j:], end-j, 0
}
// grab current and next character
c, next := grab(r, i, end), grab(r, i+1, end)
switch {
// do nothing
case c == 0:
// statement terminator
case c == ';':
break loop
// single line comments '--' and '//'
case c == '-' && next == '-', c == '/' && next == '/' && allowCComments, c == '#' && allowHashComments:
if i != 0 {
s, words = appendUpperRunes(s, r[:i], ' '), words+1
}
// find line end
if i, _ = findRune(r, i, end, '\n'); i >= end {
break
}
r, end, i = r[i+1:], end-i-1, -1
// multiline comments '/*' '*/'
case c == '/' && next == '*' && allowMultilineComments:
if i != 0 {
s, words = appendUpperRunes(s, r[:i]), words+1
}
// find comment end '*/'
for i += 2; i < end; i++ {
if grab(r, i, end) == '*' && grab(r, i+1, end) == '/' {
r, end, i = r[i+2:], end-i-2, -1
break
}
}
// add space when remaining runes begin with space, and previous
// captured word did not
if sl := len(s); end > 0 && sl != 0 && isSpaceOrControl(r[0]) && !isSpaceOrControl(s[sl-1]) {
s = append(s, ' ')
}
// end of statement, max words, or punctuation that can be ignored
case words == n || !unicode.IsLetter(c):
break loop
// ignore remaining, as no prefix can come after
case next != '/' && !unicode.IsLetter(next):
s, words = appendUpperRunes(s, r[:i+1], ' '), words+1
if next == 0 {
break
}
if next == ';' {
break loop
}
r, end, i = r[i+2:], end-i-2, -1
}
}
// trim right ' ', if any
if sl := len(s); sl != 0 && s[sl-1] == ' ' {
return string(s[:sl-1])
}
return string(s)
}
// FindPrefix finds the first 6 prefix words in s.
func FindPrefix(s string, allowCComments, allowHashComments, allowMultilineComments bool) string {
return findPrefix([]rune(s), prefixCount, allowCComments, allowHashComments, allowMultilineComments)
}
// substitute substitutes n runes in r starting at i with the runes in s.
// Dynamically grows r if necessary.
func substitute(r []rune, i, end, n int, s string) ([]rune, int) {
sr, rcap := []rune(s), cap(r)
sn := len(sr)
// grow ...
tlen := len(r) + sn - n
if tlen > rcap {
z := make([]rune, tlen)
copy(z, r)
r = z
} else {
r = r[:rcap]
}
// substitute
copy(r[i+sn:], r[i+n:])
copy(r[i:], sr)
return r[:tlen], tlen
}
// appendUpperRunes creates a new []rune from s, with the runes in r on the end
// converted to upper case. extra runes will be appended to the final []rune.
func appendUpperRunes(s []rune, r []rune, extra ...rune) []rune {
sl, rl, el := len(s), len(r), len(extra)
sre := make([]rune, sl+rl+el)
copy(sre[:sl], s)
for i := 0; i < rl; i++ {
sre[sl+i] = unicode.ToUpper(r[i])
}
copy(sre[sl+rl:], extra)
return sre
}
================================================
FILE: stmt/parse_test.go
================================================
package stmt
import (
"reflect"
"strconv"
"strings"
"testing"
)
func TestGrab(t *testing.T) {
tests := []struct {
s string
i int
exp rune
}{
{"", 0, 0},
{"a", 0, 'a'},
{" a", 0, ' '},
{"a ", 1, ' '},
{"a", 1, 0},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
z := []rune(test.s)
r := grab(z, test.i, len(z))
if r != test.exp {
t.Errorf("expected %c, got: %c", test.exp, r)
}
})
}
}
func TestFindSpace(t *testing.T) {
tests := []struct {
s string
i int
exp int
b bool
}{
{"", 0, 0, false},
{" ", 0, 0, true},
{"a", 0, 1, false},
{"a ", 0, 1, true},
{" a ", 0, 0, true},
{"aaa", 0, 3, false},
{" a ", 1, 2, true},
{"aaa", 1, 3, false},
{" aaa", 1, 4, false},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
z := []rune(test.s)
n, b := findSpace(z, test.i, len(z))
if n != test.exp {
t.Errorf("expected %d, got: %d", test.exp, n)
}
if b != test.b {
t.Errorf("expected %t, got: %t", test.b, b)
}
})
}
}
func TestFindNonSpace(t *testing.T) {
tests := []struct {
s string
i int
exp int
b bool
}{
{"", 0, 0, false},
{" ", 0, 1, false},
{"a", 0, 0, true},
{"a ", 0, 0, true},
{" a ", 0, 1, true},
{" ", 0, 4, false},
{" a ", 1, 1, true},
{"aaa", 1, 1, true},
{" aaa", 1, 1, true},
{" aa", 1, 2, true},
{" ", 1, 4, false},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
z := []rune(test.s)
n, b := findNonSpace(z, test.i, len(z))
if n != test.exp {
t.Errorf("expected %d, got: %d", test.exp, n)
}
if b != test.b {
t.Errorf("expected %t, got: %t", test.b, b)
}
})
}
}
func TestIsEmptyLine(t *testing.T) {
tests := []struct {
s string
i int
exp bool
}{
{"", 0, true},
{"a", 0, false},
{" a", 0, false},
{" a ", 0, false},
{" \na", 0, false},
{" \n\ta", 0, false},
{"a ", 1, true},
{" a", 1, false},
{" a ", 1, false},
{" \na", 1, false},
{" \n\t ", 1, true},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
z := []rune(test.s)
b := isEmptyLine(z, test.i, len(z))
if b != test.exp {
t.Errorf("expected %t, got: %t", test.exp, b)
}
})
}
}
func TestReadString(t *testing.T) {
tests := []struct {
s string
i int
exp string
ok bool
}{
{`'`, 0, ``, false},
{` '`, 1, ``, false},
{`''`, 0, `''`, true},
{`'foo' `, 0, `'foo'`, true},
{` 'foo' `, 1, `'foo'`, true},
{`"foo"`, 0, `"foo"`, true},
{"`foo`", 0, "`foo`", true},
{"`'foo'`", 0, "`'foo'`", true},
{`'foo''foo'`, 0, `'foo''foo'`, true},
{` 'foo''foo' `, 1, `'foo''foo'`, true},
{` "foo''foo" `, 1, `"foo''foo"`, true},
// escaped \" aren't allowed in strings, so the second " would be next
// double quoted string
{`"foo\""`, 0, `"foo\"`, true},
{` "foo\"" `, 1, `"foo\"`, true},
{`''''`, 0, `''''`, true},
{` '''' `, 1, `''''`, true},
{`''''''`, 0, `''''''`, true},
{` '''''' `, 1, `''''''`, true},
{`'''`, 0, ``, false},
{` ''' `, 1, ``, false},
{`'''''`, 0, ``, false},
{` ''''' `, 1, ``, false},
{`"fo'o"`, 0, `"fo'o"`, true},
{` "fo'o" `, 1, `"fo'o"`, true},
{`"fo''o"`, 0, `"fo''o"`, true},
{` "fo''o" `, 1, `"fo''o"`, true},
{`'本門台初埼本門台初埼'`, 0, `'本門台初埼本門台初埼'`, true},
{` '本門台初埼本門台初埼' `, 1, `'本門台初埼本門台初埼'`, true},
{`"本門台初埼本門台初埼"`, 0, `"本門台初埼本門台初埼"`, true},
{` "本門台初埼本門台初埼" `, 1, `"本門台初埼本門台初埼"`, true},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
r := []rune(test.s)
c, end := rune(strings.TrimSpace(test.s)[0]), len(r)
if c != '\'' && c != '"' && c != '`' {
t.Fatal("incorrect!")
}
pos, ok := readString(r, test.i+1, end, c, "")
if ok != test.ok {
t.Fatalf("expected ok %t, got: %t", test.ok, ok)
}
if !test.ok {
return
}
if r[pos] != c {
t.Fatalf("expected last character to be %c, got: %c", c, r[pos])
}
v := string(r[test.i : pos+1])
if n := len(v); n < 2 {
t.Fatalf("expected result of at least length 2, got: %d", n)
}
if v != test.exp {
t.Errorf("expected %q, got: %q", test.exp, v)
}
})
}
}
func TestReadCommand(t *testing.T) {
tests := []struct {
s string
i int
exp string
}{
{`\c foo bar z`, 0, `\c| foo bar z|`},
{`\c foo bar z `, 0, `\c| foo bar z |`},
{`\c foo bar z `, 0, `\c| foo bar z |`},
{`\c foo bar z `, 0, `\c| foo bar z |`},
{`\c pg://blah bar z `, 0, `\c| pg://blah bar z |`},
{`\foo pg://blah bar z `, 0, `\foo| pg://blah bar z |`},
{`\a\b`, 0, `\a||\b`},
{`\a \b`, 0, `\a| |\b`},
{"\\a \n\\b", 0, "\\a| |\n\\b"},
{` \ab \bc \cd `, 5, `\bc| |\cd `},
{`\p foo \p`, 0, `\p| foo |\p`},
{`\p foo \p bar`, 0, `\p| foo |\p bar`},
{`\p\p`, 0, `\p||\p`},
{`\p \r foo`, 0, `\p| |\r foo`},
{`\print \reset foo`, 0, `\print| |\reset foo`},
{`\print \reset foo`, 9, `\reset| foo|`},
{`\print \reset foo `, 9, `\reset| foo |`},
{`\print \reset foo bar `, 9, `\reset| foo bar |`},
{`\c 'foo bar' z`, 0, `\c| 'foo bar' z|`},
{`\c foo "bar " z `, 0, `\c| foo "bar " z |`},
{"\\c `foo bar z ` ", 0, "\\c| `foo bar z ` |"},
{`\c 'foob':foo:bar'test' `, 0, `\c| 'foob':foo:bar'test' |`},
{"\\a \n\\b\\c\n", 0, "\\a| |\n\\b\\c\n"},
{`\a'foob' \b`, 0, `\a'foob'| |\b`},
{`\foo 'test' "bar"\print`, 0, `\foo| 'test' "bar"|\print`},
{`\foo 'test' "bar" \print`, 0, `\foo| 'test' "bar" |\print`},
{`\afoob' \b`, 0, `\afoob'| |\b`},
{`\afoob' '\b `, 0, `\afoob'| '\b |`},
{`\afoob' '\b '\print`, 0, `\afoob'| '\b '|\print`},
{`\afoob' '\b ' \print`, 0, `\afoob'| '\b ' |\print`},
{`\afoob' '\b ' \print `, 0, `\afoob'| '\b ' |\print `},
{"\\foo `foob'foob'\\print", 0, "\\foo| `foob'foob'\\print|"},
{"\\foo `foob'foob' \\print", 0, "\\foo| `foob'foob' \\print|"},
{`\foo "foob'foob'\\print`, 0, `\foo| "foob'foob'\\print|`},
{`\foo "foob'foob' \\print`, 0, `\foo| "foob'foob' \\print|`},
{`\foo "\""\print`, 0, `\foo| "\""|\print`},
{`\foo "\"'"\print`, 0, `\foo| "\"'"|\print`},
{`\foo "\"''"\print`, 0, `\foo| "\"''"|\print`},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
z := []rune(test.s)
if !strings.Contains(test.exp, "|") {
t.Fatalf("expected value is invalid (missing |): %q", test.exp)
}
v := strings.Split(test.exp, "|")
if len(v) != 3 {
t.Fatalf("should have 3 expected values, has: %d", len(v))
}
cmd, params := readCommand(z, test.i, len(z))
if s := string(z[test.i:cmd]); s != v[0] {
t.Errorf("expected command to be %q, got: %q [%d, %d]", v[0], s, cmd, params)
}
if s := string(z[cmd:params]); s != v[1] {
t.Errorf("expected params to be %q, got: %q [%d, %d]", v[1], s, cmd, params)
}
if s := string(z[params:]); s != v[2] {
t.Errorf("expected remaining to be %q, got: %q", v[2], s)
}
})
}
}
func TestFindPrefix(t *testing.T) {
tests := []struct {
s string
w int
exp string
}{
{"", 4, ""},
{" ", 4, ""},
{" ", 4, ""},
{" select ", 4, "SELECT"},
{" select to ", 4, "SELECT TO"},
{" select to ", 4, "SELECT TO"},
{" select to ", 4, "SELECT TO"},
{"select into from", 2, "SELECT INTO"},
{"select into * from", 4, "SELECT INTO"},
{" select into * from ", 4, "SELECT INTO"},
{" select \t into \n * \t\t\n\n\n from ", 4, "SELECT INTO"},
{" select\n\n\tb\t\tzfrom j\n\n ", 2, "SELECT B"},
{"select/* foob */into", 4, "SELECTINTO"},
{"select/* foob */\tinto", 4, "SELECT INTO"},
{"select/* foob */ into", 4, "SELECT INTO"},
{"select/* foob */ into ", 4, "SELECT INTO"},
{"select /* foob */ into ", 4, "SELECT INTO"},
{" select /* foob */ into ", 4, "SELECT INTO"},
{" select * --test\n from where \n\nfff", 4, "SELECT"},
{"/*idreamedital*/foo//bar\n/* nothing */test\n\n\nwe made /*\n\n\n\n*/ \t it ", 5, "FOO TEST WE MADE IT"},
{" --yes\n//no\n\n\t/*whatever*/ ", 4, ""},
{"/*/*test*/*/ select ", 4, ""},
{"/*/*test*/*/ select ", 4, ""},
{"//", 4, ""},
{"-", 4, ""},
{"* select", 4, ""},
{"/**/", 4, ""},
{"--\n\t\t\thello,\t--", 4, "HELLO"},
{"/* */\n\n\n\tselect/*--\n*/\t\b\bzzz", 4, "SELECT ZZZ"},
{"n\nn\n\nn\tn", 7, "N N N N"},
{"n\nn\n\nn\tn", 1, "N"},
{"--\n/* */n/* */\nn\n--\nn\tn", 7, "N N N N"},
{"--\n/* */n\n/* */\nn\n--\nn\tn", 7, "N N N N"},
{"\n\n/* */\nn n", 7, "N N"},
{"\n\n/* */\nn/* */n", 7, "NN"},
{"\n\n/* */\nn /* */n", 7, "N N"},
{"\n\n/* */\nn/* */\nn", 7, "N N"},
{"\n\n/* */\nn/* */ n", 7, "N N"},
{"*/foob", 7, ""},
{"*/ \n --\nfoob", 7, ""},
{"--\n\n--\ntest", 7, "TEST"},
{"\b\btest", 7, "TEST"},
{"select/*\r\n\r\n*/blah", 7, "SELECTBLAH"},
{"\r\n\r\nselect from where", 8, "SELECT FROM WHERE"},
{"\r\n\b\bselect 1;create 2;", 8, "SELECT"},
{"\r\n\bbegin transaction;\ncreate x where;", 8, "BEGIN TRANSACTION"},
{"begin;test;create;awesome", 3, "BEGIN"},
{" /* */ ; begin; ", 5, ""},
{" /* foo */ test; test", 5, "TEST"},
{";test", 5, ""},
{"\b\b\t;test", 5, ""},
{"\b\t; test", 5, ""},
{"\b\tfoob; test", 5, "FOOB"},
{" TEST /*\n\t\b*/\b\t;foob", 10, "TEST"},
{"begin transaction\n\tinsert into x;\ncommit;", 6, "BEGIN TRANSACTION INSERT INTO X"},
{"--\nbegin /* */transaction/* */\n/* */\tinsert into x;--/* */\ncommit;", 6, "BEGIN TRANSACTION INSERT INTO X"},
{"#\nbegin /* */transaction/* */\n/* */\t#\ninsert into x;#\n--/* */\ncommit;", 6, "BEGIN TRANSACTION INSERT INTO X"},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
if p := findPrefix([]rune(test.s), test.w, true, true, true); p != test.exp {
t.Errorf("%q expected %q, got: %q", test.s, test.exp, p)
}
})
}
}
func TestReadVar(t *testing.T) {
tests := []struct {
s string
i int
exp *Var
}{
{``, 0, nil},
{`:`, 0, nil},
{` :`, 0, nil},
{`a:`, 0, nil},
{`a:a`, 0, nil},
{`: `, 0, nil},
{`: a `, 0, nil},
{`:'ab ' `, 0, nil},
{`:"ab " `, 0, nil},
{`:{?ab } `, 0, nil},
{`:a`, 0, v(0, `a`)},
{`:ab`, 0, v(0, `ab`)},
{`:a `, 0, v(0, `a`)},
{`:a_ `, 0, v(0, `a_`)},
{":a_\t ", 0, v(0, `a_`)},
{":a_\n ", 0, v(0, `a_`)},
{`:a9`, 0, v(0, `a9`)},
{`:ab9`, 0, v(0, `ab9`)},
{`:a 9`, 0, v(0, `a`)},
{`:a_9 `, 0, v(0, `a_9`)},
{":a_9\t ", 0, v(0, `a_9`)},
{":a_9\n ", 0, v(0, `a_9`)},
{`:a_;`, 0, v(0, `a_`)},
{`:a_\`, 0, v(0, `a_`)},
{`:a_$`, 0, v(0, `a_`)},
{`:a_'`, 0, v(0, `a_`)},
{`:a_"`, 0, v(0, `a_`)},
{`:ab `, 0, v(0, `ab`)},
{`:ab123 `, 0, v(0, `ab123`)},
{`:ab123`, 0, v(0, `ab123`)},
{`:'`, 0, nil},
{`:' `, 0, nil},
{`:' a`, 0, nil},
{`:' a `, 0, nil},
{`:"`, 0, nil},
{`:" `, 0, nil},
{`:" a`, 0, nil},
{`:" a `, 0, nil},
{`:''`, 0, nil},
{`:'' `, 0, nil},
{`:'' a`, 0, nil},
{`:""`, 0, nil},
{`:"" `, 0, nil},
{`:"" a`, 0, nil},
{`:' `, 0, nil},
{`:' `, 0, nil},
{`:" `, 0, nil},
{`:" `, 0, nil},
{`:'a'`, 0, v(0, `a`, `'`)},
{`:'a' `, 0, v(0, `a`, `'`)},
{`:'ab'`, 0, v(0, `ab`, `'`)},
{`:'ab' `, 0, v(0, `ab`, `'`)},
{`:"a"`, 0, v(0, `a`, `"`)},
{`:"a" `, 0, v(0, `a`, `"`)},
{`:"ab"`, 0, v(0, `ab`, `"`)},
{`:"ab" `, 0, v(0, `ab`, `"`)},
{`:型`, 0, v(0, "型")},
{`:'型'`, 0, v(0, "型", `'`)},
{`:"型"`, 0, v(0, "型", `"`)},
{` :型 `, 1, v(1, "型")},
{` :'型' `, 1, v(1, "型", `'`)},
{` :"型" `, 1, v(1, "型", `"`)},
{`:型示師`, 0, v(0, "型示師")},
{`:'型示師'`, 0, v(0, "型示師", `'`)},
{`:"型示師"`, 0, v(0, "型示師", `"`)},
{` :型示師 `, 1, v(1, "型示師")},
{` :'型示師' `, 1, v(1, "型示師", `'`)},
{` :"型示師" `, 1, v(1, "型示師", `"`)},
{`:{?a}`, 0, v(0, "a", `?`)},
{` :{?a} `, 1, v(1, "a", `?`)},
{`:{?a_b} `, 0, v(0, "a_b", `?`)},
{` :{?a_b} `, 1, v(1, "a_b", `?`)},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Logf("parsing %q", test.s)
z := []rune(test.s)
v := readVar(z, test.i, len(z), grab(z, test.i+1, len(z)))
if !reflect.DeepEqual(v, test.exp) {
t.Errorf("\nexpected: %#v\n got: %#v", test.exp, v)
}
if test.exp != nil && v != nil {
n := string(z[v.I+1 : v.End])
switch v.Quote {
case '\'', '"':
if c := rune(n[0]); c != v.Quote {
t.Errorf("expected var to start with quote %c, got: %c", c, v.Quote)
}
if c := rune(n[len(n)-1]); c != v.Quote {
t.Errorf("expected var to end with quote %c, got: %c", c, v.Quote)
}
n = n[1 : len(n)-1]
case '?':
if !strings.HasPrefix(n, "{?") {
t.Errorf("expected var %q to start with {?", n)
}
if !strings.HasSuffix(n, "}") {
t.Errorf("expected var %q to end with }", n)
}
n = n[2 : len(n)-1]
}
if n != test.exp.Name {
t.Errorf("expected var name of %q, got: %q", test.exp.Name, n)
}
}
})
}
}
func TestSubstitute(t *testing.T) {
a512 := sl(512, 'a')
b512 := sl(512, 'a')
b512 = b512[:1] + "b" + b512[2:]
if len(b512) != 512 {
t.Fatalf("b512 should be length 512, is: %d", len(b512))
}
tests := []struct {
s string
i int
n int
t string
exp string
}{
{"", 0, 0, "", ""},
{"a", 0, 1, "b", "b"},
{"ab", 1, 1, "cd", "acd"},
{"", 0, 0, "ab", "ab"},
{"abc", 1, 2, "d", "ad"},
{a512, 1, 1, "b", b512},
{"foo", 0, 1, "bar", "baroo"},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
r := []rune(test.s)
r, rlen := substitute(r, test.i, len(r), test.n, test.t)
if rlen != len(test.exp) {
t.Errorf("expected length %d, got: %d", len(test.exp), rlen)
}
if s := string(r); s != test.exp {
t.Errorf("expected %q, got %q", test.exp, s)
}
})
}
}
================================================
FILE: stmt/stmt.go
================================================
// Package stmt contains a statement buffer implementation.
package stmt
import (
"bytes"
"unicode"
)
// minCapIncrease is the minimum amount by which to grow a Stmt.Buf.
const minCapIncrease = 512
// Stmt is a reusable statement buffer that handles reading and parsing
// SQL-like statements.
type Stmt struct {
// f is the rune source.
f func() ([]rune, error)
// allowDollar allows dollar quoted strings (ie, $$ ... $$ or $tag$ ... $tag$).
allowDollar bool
// allowMultilineComments allows multiline comments (ie, /* ... */)
allowMultilineComments bool
// allowCComments allows C-style comments (ie, // ... )
allowCComments bool
// allowHashComments allows hash comments (ie, # ... )
allowHashComments bool
// Buf is the statement buffer
Buf []rune
// Len is the current len of any statement in Buf.
Len int
// Prefix is the detected prefix of the statement.
Prefix string
// Vars is the list of encountered variables.
Vars []*Var
// r is the unprocessed runes.
r []rune
// rlen is the number of unprocessed runes.
rlen int
// quote indicates currently parsing a quoted string.
quote rune
// quoteDollarTag is the parsed tag of a dollar quoted string
quoteDollarTag string
// multilineComment is state of multiline comment processing
multilineComment bool
// balanceCount is the balanced paren count
balanceCount int
// ready indicates that a complete statement has been parsed
ready bool
}
// New creates a new Stmt using the supplied rune source f.
func New(f func() ([]rune, error), opts ...Option) *Stmt {
b := &Stmt{
f: f,
}
// apply opts
for _, o := range opts {
o(b)
}
return b
}
// String satisfies fmt.Stringer.
func (b *Stmt) String() string {
return string(b.Buf)
}
// PrintString returns a print string of the statement buffer, which is the
// statement buffer but with escaped variables re-interpolated.
func (b *Stmt) PrintString() string {
if b.Len == 0 {
return ""
}
i, s, w := 0, string(b.Buf), new(bytes.Buffer)
// deinterpolate vars
for _, v := range b.Vars {
if v.Quote != '\\' {
continue
}
if len(s) > i {
w.WriteString(s[i:v.I])
}
w.WriteString(v.String())
i = v.I + v.Len
}
// add remaining
if len(s) > i {
w.WriteString(s[i:])
}
return w.String()
}
// RawString returns the non-interpolated version of the statement buffer.
func (b *Stmt) RawString() string {
if b.Len == 0 {
return ""
}
i, s, z := 0, string(b.Buf), new(bytes.Buffer)
// deinterpolate vars
for _, v := range b.Vars {
if !v.Defined && v.Quote != '\\' {
continue
}
if len(s) > i {
z.WriteString(s[i:v.I])
}
z.WriteString(v.String())
i = v.I + v.Len
}
// add remaining
if len(s) > i {
z.WriteString(s[i:])
}
return z.String()
}
// Ready returns true when the statement buffer contains a non empty, balanced
// statement that has been properly terminated (ie, ended with a semicolon).
func (b *Stmt) Ready() bool {
return b.ready
}
// Reset resets the statement buffer.
func (b *Stmt) Reset(r []rune) {
// reset buf
b.Buf, b.Len, b.Prefix, b.Vars = nil, 0, "", b.Vars[:0]
// quote state
b.quote, b.quoteDollarTag = 0, ""
// multicomment state
b.multilineComment = false
// balance state
b.balanceCount = 0
// ready state
b.ready = false
if r != nil {
b.r, b.rlen = r, len(r)
}
}
// Next reads the next statement from the rune source, returning when either
// the statement has been terminated, or a meta command has been read from the
// rune source. After a call to Next, the collected statement is available in
// Stmt.Buf, or call Stmt.String() to convert it to a string.
//
// After a call to Next, Reset should be called if the extracted statement was
// executed (ie, processed). Note that the rune source supplied to New will be
// called again only after any remaining collected runes have been processed.
//
// Example:
//
// buf := stmt.New(runeSrc)
// for {
// cmd, params, err := buf.Next(unquoteFunc)
// if err { /* ... */ }
//
// execute, quit := buf.Ready() || cmd == "g", cmd == "q"
//
// // process command ...
// switch cmd {
// /* ... */
// }
//
// if quit {
// break
// }
//
// if execute {
// s := buf.String()
// res, err := db.Query(s)
// /* handle database ... */
// buf.Reset(nil)
// }
// }
func (b *Stmt) Next(unquote func(string, bool) (string, bool, error)) (string, string, error) {
var err error
var i int
// no runes to process, grab more
if b.rlen == 0 {
b.r, err = b.f()
if err != nil {
return "", "", err
}
b.rlen = len(b.r)
}
var cmd, params string
var ok bool
parse:
for ; i < b.rlen; i++ {
// fmt.Fprintf(os.Stderr, "> %d: `%s`\n", i, string(b.r[i:]))
// grab c, next
c, next := b.r[i], grab(b.r, i+1, b.rlen)
switch {
// find end of string
case b.quote != 0:
i, ok = readString(b.r, i, b.rlen, b.quote, b.quoteDollarTag)
if ok {
b.quote, b.quoteDollarTag = 0, ""
}
// find end of multiline comment
case b.multilineComment:
i, ok = readMultilineComment(b.r, i, b.rlen)
b.multilineComment = !ok
// start of single or double quoted string
case c == '\'' || c == '"':
b.quote = c
// start of dollar quoted string literal (postgres)
case b.allowDollar && c == '$' && (next == '$' || next == '_' || unicode.IsLetter(next)):
var id string
id, i, ok = readDollarAndTag(b.r, i, b.rlen)
if ok {
b.quote, b.quoteDollarTag = '$', id
}
// start of sql comment, skip to end of line
case c == '-' && next == '-':
i = b.rlen
// start of c-style comment, skip to end of line
case b.allowCComments && c == '/' && next == '/':
i = b.rlen
// start of hash comment, skip to end of line
case b.allowHashComments && c == '#':
i = b.rlen
// start of multiline comment
case b.allowMultilineComments && c == '/' && next == '*':
b.multilineComment = true
i++
// variable declaration
case c == ':' && next != ':':
if v := readVar(b.r, i, b.rlen, next); v != nil {
b.Vars = append(b.Vars, v)
z, ok, _ := unquote(v.Name, true)
if v.Defined = ok || v.Quote == '?'; v.Defined {
b.r, b.rlen = v.Substitute(b.r, z, ok)
}
if b.Len != 0 {
v.I += b.Len + 1
}
}
// skip escaped backslash, semicolon, colon
case c == '\\' && (next == '\\' || next == ';' || next == ':'):
v := &Var{
I: i,
End: i + 2,
Quote: '\\',
Name: string(next),
}
b.Vars = append(b.Vars, v)
if b.r, b.rlen = v.Substitute(b.r, string(next), false); b.Len != 0 {
v.I += b.Len + 1
}
// unbalance
case c == '(':
b.balanceCount++
// balance
case c == ')':
b.balanceCount = max(0, b.balanceCount-1)
// continue processing quoted string, multiline comment, or unbalanced statements
case b.quote != 0 || b.multilineComment || b.balanceCount != 0:
// start of command
case c == '\\':
// parse command and params end positions
cend, pend := readCommand(b.r, i, b.rlen)
cmd, params = string(b.r[i:cend]), string(b.r[cend:pend])
// remove command and params from r
b.r = append(b.r[:i], b.r[pend:]...)
b.rlen = len(b.r)
break parse
// terminated
case c == ';':
b.ready = true
i++
break parse
}
}
// fix i -- i will be +1 when passing the length, which is a problem as the
// '\n' will get copied from the source.
i = min(i, b.rlen)
// append line to buf when:
// 1. in a quoted string (ie, ', ", or $)
// 2. in a multiline comment
// 3. line is not empty
//
// DO NOT append to buf when:
// 1. line is empty/whitespace and not in a string/multiline comment
empty := isEmptyLine(b.r, 0, i)
appendLine := b.quote != 0 || b.multilineComment || !empty
if !b.multilineComment && cmd != "" && empty {
appendLine = false
}
if appendLine {
// skip leading space when empty
st := 0
if b.Len == 0 {
st, _ = findNonSpace(b.r, 0, i)
}
// log.Printf(">> appending: `%s`", string(r[st:i]))
b.Append(b.r[st:i], lineend)
}
// set prefix
b.Prefix = findPrefix(b.Buf, prefixCount, b.allowCComments, b.allowHashComments, b.allowMultilineComments)
// reset r
b.r = b.r[i:]
b.rlen = len(b.r)
/*
fmt.Fprintf(os.Stderr, "\n------------------------------\n")
fmt.Fprintf(os.Stderr, " NEXT: `%s`\n", string(b.Buf))
fmt.Fprintf(os.Stderr, " REMAIN: `%s`\n", string(b.r))
fmt.Fprintf(os.Stderr, " CMD: `%s`\n", cmd)
fmt.Fprintf(os.Stderr, " PARAMS: %v\n", params)
*/
return cmd, params, nil
}
// Append appends r to b.Buf separated by sep when b.Buf is not already empty.
//
// Dynamically grows b.Buf as necessary to accommodate r and the separator.
// Specifically, when b.Buf is not empty, b.Buf will grow by increments of
// MinCapIncrease.
//
// After a call to Append, b.Len will be len(b.Buf)+len(sep)+len(r). Call Reset
// to reset the Buf.
func (b *Stmt) Append(r, sep []rune) {
rlen := len(r)
// initial
if b.Buf == nil {
b.Buf, b.Len = r, rlen
return
}
blen, seplen := b.Len, len(sep)
tlen := blen + rlen + seplen
// grow
if bcap := cap(b.Buf); tlen > bcap {
n := tlen + 2*rlen
n += minCapIncrease - (n % minCapIncrease)
z := make([]rune, blen, n)
copy(z, b.Buf)
b.Buf = z
}
b.Buf = b.Buf[:tlen]
copy(b.Buf[blen:], sep)
copy(b.Buf[blen+seplen:], r)
b.Len = tlen
}
// AppendString is a util func wrapping Append.
func (b *Stmt) AppendString(s, sep string) {
b.Append([]rune(s), []rune(sep))
}
// State returns a string representing the state of statement parsing.
func (b *Stmt) State() string {
switch {
case b.quote != 0:
return string(b.quote)
case b.multilineComment:
return "*"
case b.balanceCount != 0:
return "("
case b.Len != 0:
return "-"
}
return "="
}
// Var holds information about a variable.
type Var struct {
// I is where the variable starts (ie, ':') in Stmt.Buf.
I int
// End is where the variable ends in Stmt.Buf.
End int
// Quote is the quote character used if the variable was quoted, 0
// otherwise.
Quote rune
// Name is the actual variable name excluding ':' and any enclosing quote
// characters.
Name string
// Len is the length of the replaced variable.
Len int
// Defined indicates whether the variable has been defined.
Defined bool
}
// String satisfies the fmt.Stringer interface.
func (v *Var) String() string {
switch v.Quote {
case '\\':
return "\\" + v.Name
case '\'', '"':
return ":" + string(v.Quote) + v.Name + string(v.Quote)
case '?':
return ":{?" + v.Name + "}"
}
return ":" + v.Name
}
// Substitute substitutes part of r, with s.
func (v *Var) Substitute(r []rune, s string, ok bool) ([]rune, int) {
switch v.Quote {
case '?':
s = trueFalse(ok)
case '\'', '"':
s = string(v.Quote) + s + string(v.Quote)
}
// fmt.Fprintf(os.Stderr, "orig: %q repl: %q\n", string(r), s)
sr, rcap := []rune(s), cap(r)
v.Len = len(sr)
// grow ...
tlen := len(r) + v.Len - (v.End - v.I)
if tlen > rcap {
z := make([]rune, tlen)
copy(z, r)
r = z
} else {
r = r[:rcap]
}
// substitute
copy(r[v.I+v.Len:], r[v.End:])
copy(r[v.I:v.I+v.Len], sr)
return r[:tlen], tlen
}
// Option is a statement buffer option.
type Option func(*Stmt)
// WithAllowDollar is a statement buffer option to set allowing dollar strings (ie,
// $$text$$ or $tag$text$tag$).
func WithAllowDollar(enable bool) Option {
return func(b *Stmt) {
b.allowDollar = enable
}
}
// WithAllowMultilineComments is a statement buffer option to set allowing multiline comments
// (ie, /* ... */).
func WithAllowMultilineComments(enable bool) Option {
return func(b *Stmt) {
b.allowMultilineComments = enable
}
}
// WithAllowCComments is a statement buffer option to set allowing C-style comments
// (ie, // ...).
func WithAllowCComments(enable bool) Option {
return func(b *Stmt) {
b.allowCComments = enable
}
}
// WithAllowHashComments is a statement buffer option to set allowing hash comments
// (ie, # ...).
func WithAllowHashComments(enable bool) Option {
return func(b *Stmt) {
b.allowHashComments = enable
}
}
// isSpaceOrControl is a special test for either a space or a control (ie, \b)
// characters.
func isSpaceOrControl(r rune) bool {
return unicode.IsSpace(r) || unicode.IsControl(r)
}
// lastIndex returns the last index in r of needle, or -1 if not found.
func lastIndex(r []rune, needle rune) int {
for i := len(r) - 1; i >= 0; i-- {
if r[i] == needle {
return i
}
}
return -1
}
// trueFalse returns TRUE or FALSE.
func trueFalse(ok bool) string {
if ok {
return "TRUE"
}
return "FALSE"
}
// lineend is the slice to use when appending a line.
var lineend = []rune{'\n'}
================================================
FILE: stmt/stmt_test.go
================================================
package stmt
import (
"io"
"os/user"
"reflect"
"strconv"
"strings"
"testing"
"github.com/xo/usql/env"
)
func TestAppend(t *testing.T) {
a512 := sl(512, 'a')
// b1024 := sl(1024, 'b')
tests := []struct {
s []string
exp string
l int
c int
}{
{[]string{""}, "", 0, 0},
{[]string{"", ""}, "\n", 1, minCapIncrease},
{[]string{"", "", ""}, "\n\n", 2, minCapIncrease},
{[]string{"", "", "", ""}, "\n\n\n", 3, minCapIncrease},
{[]string{"a", ""}, "a\n", 2, 2},
{[]string{"a", "b", ""}, "a\nb\n", 4, minCapIncrease},
{[]string{"a", "b", "c", ""}, "a\nb\nc\n", 6, minCapIncrease},
{[]string{"", "a", ""}, "\na\n", 3, minCapIncrease},
{[]string{"", "a", "b", ""}, "\na\nb\n", 5, minCapIncrease},
{[]string{"", "a", "b", "c", ""}, "\na\nb\nc\n", 7, minCapIncrease},
{[]string{"", "foo"}, "\nfoo", 4, minCapIncrease},
{[]string{"", "foo", ""}, "\nfoo\n", 5, minCapIncrease},
{[]string{"foo", "", "bar"}, "foo\n\nbar", 8, minCapIncrease},
{[]string{"", "foo", "bar"}, "\nfoo\nbar", 8, minCapIncrease},
{[]string{a512}, a512, 512, 512},
{[]string{a512, a512}, a512 + "\n" + a512, 1025, 5 * minCapIncrease},
{[]string{a512, a512, a512}, a512 + "\n" + a512 + "\n" + a512, 1538, 5 * minCapIncrease},
{[]string{a512, ""}, a512 + "\n", 513, 2 * minCapIncrease},
{[]string{a512, "", "foo"}, a512 + "\n\nfoo", 517, 2 * minCapIncrease},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
b := new(Stmt)
for _, s := range test.s {
b.AppendString(s, "\n")
}
if s := b.String(); s != test.exp {
t.Errorf("expected result of %q, got: %q", test.exp, s)
}
if b.Len != test.l {
t.Errorf("expected resulting len of %d, got: %d", test.l, b.Len)
}
if c := cap(b.Buf); c != test.c {
t.Errorf("expected resulting cap of %d, got: %d", test.c, c)
}
b.Reset(nil)
if b.Len != 0 {
t.Errorf("expected after reset len of 0, got: %d", b.Len)
}
b.AppendString("", "\n")
if s := b.String(); s != "" {
t.Errorf("expected after reset appending an empty string would result in empty string, got: %q", s)
}
})
}
}
func TestVariedSeparator(t *testing.T) {
b := new(Stmt)
b.AppendString("foo", "\n")
b.AppendString("foo", "bar")
if b.Len != 9 {
t.Errorf("expected len of 9, got: %d", b.Len)
}
if s := b.String(); s != "foobarfoo" {
t.Errorf("expected %q, got: %q", "foobarfoo", s)
}
if c := cap(b.Buf); c != minCapIncrease {
t.Errorf("expected cap of %d, got: %d", minCapIncrease, c)
}
}
func TestNextResetState(t *testing.T) {
u, err := user.Current()
if err != nil {
t.Fatalf("expected no error, got: %v", err)
}
unquote := env.Untick(u, env.NewVars(), false)
tests := []struct {
s string
stmts []string
cmds []string
state string
vars []string
}{
{``, nil, []string{`|`}, `=`, nil},
{`;`, []string{`;`}, []string{`|`}, `=`, nil},
{` ; `, []string{`;`}, []string{`|`, `|`}, `=`, nil},
{` \v `, nil, []string{`\v| `}, `=`, nil},
{` \v \p`, nil, []string{`\v| `, `\p|`}, `=`, nil},
{` \v foo \p`, nil, []string{`\v| foo `, `\p|`}, `=`, nil},
{` \v foo bar \p zz`, nil, []string{`\v| foo bar `, `\p| zz`}, `=`, nil},
{` \very foo bar \print zz`, nil, []string{`\very| foo bar `, `\print| zz`}, `=`, nil},
{`select 1;`, []string{`select 1;`}, []string{`|`}, `=`, nil},
{`select 1\g`, []string{`select 1`}, []string{`\g|`}, `=`, nil},
{`select 1 \g`, []string{`select 1 `}, []string{`\g|`}, `=`, nil},
{` select 1 \g`, []string{`select 1 `}, []string{`\g|`}, `=`, nil},
{` select 1 \g `, []string{`select 1 `}, []string{`\g| `}, `=`, nil},
{`select 1; select 1\g`, []string{`select 1;`, `select 1`}, []string{`|`, `\g|`}, `=`, nil},
{"select 1\n\\g", []string{`select 1`}, []string{`|`, `\g|`}, `=`, nil},
{"select 1 \\g\n\n\n\n\\v", []string{`select 1 `}, []string{`\g|`, `|`, `|`, `|`, `\v|`}, `=`, nil},
{"select 1 \\g\n\n\n\n\\v foob \\p zzz \n\n", []string{`select 1 `}, []string{`\g|`, `|`, `|`, `|`, `\v| foob `, `\p| zzz `, `|`, `|`}, `=`, nil},
{" select 1 \\g \\p \n select (15)\\g", []string{`select 1 `, `select (15)`}, []string{`\g| `, `\p| `, `\g|`}, `=`, nil},
{" select 1 ( \\g ) \n ;", []string{"select 1 ( \\g ) \n ;"}, []string{`|`, `|`}, `=`, nil},
{
" select 1\n;select 2\\g select 3; \\p \\z foo bar ",
[]string{"select 1\n;", "select 2"},
[]string{`|`, `|`, `\g| select 3; `, `\p| `, `\z| foo bar `},
"=", nil,
},
{
" select 1\\g\n\n\tselect 2\\g\n select 3; \\p \\z foo bar \\p\\p select * from; \n\\p",
[]string{`select 1`, `select 2`, `select 3;`},
[]string{`\g|`, `|`, `\g|`, `|`, `\p| `, `\z| foo bar `, `\p|`, `\p| select * from; `, `\p|`},
"=", nil,
},
{"select '';", []string{"select '';"}, []string{"|"}, "=", nil},
{"select 'a''b\nz';", []string{"select 'a''b\nz';"}, []string{"|", "|"}, "=", nil},
{"select 'a' 'b\nz';", []string{"select 'a' 'b\nz';"}, []string{"|", "|"}, "=", nil},
{"select \"\";", []string{"select \"\";"}, []string{"|"}, "=", nil},
{"select \"\n\";", []string{"select \"\n\";"}, []string{"|", "|"}, "=", nil},
{"select $$$$;", []string{"select $$$$;"}, []string{"|"}, "=", nil},
{"select $$\nfoob(\n$$;", []string{"select $$\nfoob(\n$$;"}, []string{"|", "|", "|"}, "=", nil},
{"select $tag$$tag$;", []string{"select $tag$$tag$;"}, []string{"|"}, "=", nil},
{"select $tag$\n\n$tag$;", []string{"select $tag$\n\n$tag$;"}, []string{"|", "|", "|"}, "=", nil},
{"select $tag$\n(\n$tag$;", []string{"select $tag$\n(\n$tag$;"}, []string{"|", "|", "|"}, "=", nil},
{"select $tag$\n\\v(\n$tag$;", []string{"select $tag$\n\\v(\n$tag$;"}, []string{"|", "|", "|"}, "=", nil},
{"select $tag$\n\\v(\n$tag$\\g", []string{"select $tag$\n\\v(\n$tag$"}, []string{"|", "|", `\g|`}, "=", nil},
{"select $$\n\\v(\n$tag$$zz$$\\g$$\\g", []string{"select $$\n\\v(\n$tag$$zz$$\\g$$"}, []string{"|", "|", `\g|`}, "=", nil},
{"select * --\n\\v", nil, []string{"|", `\v|`}, "-", nil},
{"select--", nil, []string{"|"}, "-", nil},
{"select --", nil, []string{"|"}, "-", nil},
{"select /**/", nil, []string{"|"}, "-", nil},
{"select/* */", nil, []string{"|"}, "-", nil},
{"select/*", nil, []string{"|"}, "*", nil},
{"select /*", nil, []string{"|"}, "*", nil},
{"select * /**/", nil, []string{"|"}, "-", nil},
{"select * /* \n\n\n--*/\n;", []string{"select * /* \n\n\n--*/\n;"}, []string{"|", "|", "|", "|", "|"}, "=", nil},
{"select * /* \n\n\n--*/\n", nil, []string{"|", "|", "|", "|", "|"}, "-", nil},
{"select * /* \n\n\n--\n", nil, []string{"|", "|", "|", "|", "|"}, "*", nil},
{"\\p \\p\nselect (", nil, []string{`\p| `, `\p|`, "|"}, "(", nil},
{"\\p \\p\nselect ()", nil, []string{`\p| `, `\p|`, "|"}, "-", nil},
{"\n \t\t \n", nil, []string{"|", "|", "|"}, "=", nil},
{"\n foob \t\t \n", nil, []string{"|", "|", "|"}, "-", nil},
{"$$", nil, []string{"|"}, "$", nil},
{"$$foo", nil, []string{"|"}, "$", nil},
{"'", nil, []string{"|"}, "'", nil},
{"(((()()", nil, []string{"|"}, "(", nil},
{"\"", nil, []string{"|"}, "\"", nil},
{"\"foo", nil, []string{"|"}, "\"", nil},
{":a :b", nil, []string{"|"}, "-", []string{"a", "b"}},
{":{?a_b} :{?_foo_bar_}", nil, []string{"|"}, "-", []string{"a_b", "_foo_bar_"}},
{`select :'a_b' :"foo_bar_"`, nil, []string{"|"}, "-", []string{"a_b", "foo_bar_"}},
{`select :a:b;`, []string{"select :a:b;"}, []string{"|"}, "=", []string{"a", "b"}},
{"select :'a\n:foo:bar", nil, []string{"|", "|"}, "'", nil},
{"select :''\n:foo:bar\\g", []string{"select :''\n:foo:bar"}, []string{"|", `\g|`}, "=", []string{"foo", "bar"}},
{"select :''\n:foo :bar\\g", []string{"select :''\n:foo :bar"}, []string{"|", `\g|`}, "=", []string{"foo", "bar"}},
{"select :''\n :foo :bar \\g", []string{"select :''\n :foo :bar "}, []string{"|", `\g|`}, "=", []string{"foo", "bar"}},
{"select :'a\n:'foo':\"bar\"", nil, []string{"|", "|"}, "'", nil},
{"select :''\n:'foo':\"bar\"\\g", []string{"select :''\n:'foo':\"bar\""}, []string{"|", `\g|`}, "=", []string{"foo", "bar"}},
{"select :''\n:'foo' :\"bar\"\\g", []string{"select :''\n:'foo' :\"bar\""}, []string{"|", `\g|`}, "=", []string{"foo", "bar"}},
{"select :''\n :'foo' :\"bar\" \\g", []string{"select :''\n :'foo' :\"bar\" "}, []string{"|", `\g|`}, "=", []string{"foo", "bar"}},
{`select 1\echo 'pg://':foo'/':bar`, nil, []string{`\echo| 'pg://':foo'/':bar`}, "-", nil},
{`select :'foo'\echo 'pg://':bar'/' `, nil, []string{`\echo| 'pg://':bar'/' `}, "-", []string{"foo"}},
{`select 1\g '\g`, []string{`select 1`}, []string{`\g| '\g`}, "=", nil},
{`select 1\g "\g`, []string{`select 1`}, []string{`\g| "\g`}, "=", nil},
{"select 1\\g `\\g", []string{`select 1`}, []string{"\\g| `\\g"}, "=", nil},
{`select 1\g '\g `, []string{`select 1`}, []string{`\g| '\g `}, "=", nil},
{`select 1\g "\g `, []string{`select 1`}, []string{`\g| "\g `}, "=", nil},
{"select 1\\g `\\g ", []string{`select 1`}, []string{"\\g| `\\g "}, "=", nil},
{"select $$\\g$$\\g", []string{`select $$\g$$`}, []string{`\g|`}, "=", nil},
{"select $1\\bind a b c\\g", []string{`select $1`}, []string{`\bind| a b c`, `\g|`}, "=", nil},
{"select $1 \\bind a b c \\g", []string{`select $1 `}, []string{`\bind| a b c `, `\g|`}, "=", nil},
{"select $2, $a$ foo $a$, $1 \\bind a b \\g", []string{`select $2, $a$ foo $a$, $1 `}, []string{`\bind| a b `, `\g|`}, "=", nil},
{"select \\;\\\\\\:\\; \n;", []string{"select ;\\:; \n;"}, []string{`|`, `|`}, `=`, []string{`;`, `\`, `:`, `;`}},
{"select \\;\\;\\;\\; \n;", []string{"select ;;;; \n;"}, []string{`|`, `|`}, `=`, []string{`;`, `;`, `;`, `;`}},
{`select \;\;\;\;;`, []string{`select ;;;;;`}, []string{`|`}, `=`, []string{`;`, `;`, `;`, `;`}},
{`select \\\;\\\;\\\;\\\;;`, []string{`select \;\;\;\;;`}, []string{`|`}, `=`, []string{`\`, `;`, `\`, `;`, `\`, `;`, `\`, `;`}},
{`select \:foo;`, []string{`select :foo;`}, []string{`|`}, `=`, []string{":"}},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Logf("statement: %q", test.s)
b := New(
sp(test.s, "\n"),
WithAllowDollar(true),
WithAllowMultilineComments(true),
WithAllowCComments(true),
)
var stmts, cmds, aparams []string
var vars []*Var
loop:
for {
cmd, params, err := b.Next(unquote)
t.Logf("next buf:%q", string(b.Buf))
t.Logf("next cmd:%q params:%q", cmd, params)
switch {
case err == io.EOF:
break loop
case err != nil:
t.Fatalf("expected no error, got: %v", err)
}
if b.Ready() || cmd == `\g` {
stmts = append(stmts, b.String())
vars = append(vars, b.Vars...)
b.Reset(nil)
}
cmds = append(cmds, cmd)
aparams = append(aparams, params)
}
vars = append(vars, b.Vars...)
if len(stmts) != len(test.stmts) || !reflect.DeepEqual(stmts, test.stmts) {
t.Errorf("expected %d statements, got: %d", len(test.stmts), len(stmts))
t.Logf("expected:")
for _, s := range test.stmts {
t.Logf(" %q", s)
}
t.Logf("got:")
for _, s := range stmts {
t.Logf(" %q", s)
}
}
if cz := cc(cmds, aparams); !reflect.DeepEqual(cz, test.cmds) {
t.Logf(">> cmds: %#v, aparams: %#v, cz: %#v, test.cmds: %#v", cmds, aparams, cz, test.cmds)
t.Errorf("commands do not match")
t.Logf("expected:")
for _, s := range test.cmds {
t.Logf(" %q", s)
}
t.Logf("got:")
for _, s := range cz {
t.Logf(" %q", s)
}
}
if st := b.State(); st != test.state {
t.Errorf("expected end parse state %q, got: %q", test.state, st)
}
if len(vars) != len(test.vars) {
t.Errorf("expected %d vars, got: %d", len(test.vars), len(vars))
t.Logf("expected:")
for _, v := range test.vars {
t.Logf(" %#v", v)
}
t.Logf("got:")
for _, v := range vars {
t.Logf(" %#v", v)
}
}
for i, v := range test.vars {
if len(vars) < i {
t.Logf("expected var %d: %#v", i, v)
continue
}
if vars[i].Name != v {
t.Errorf("expected var %d: %q, got: %q", i, v, vars[i].Name)
}
}
b.Reset(nil)
if len(b.Buf) != 0 {
t.Errorf("after reset b.Buf should have len %d, got: %d", 0, len(b.Buf))
}
if b.Len != 0 {
t.Errorf("after reset should have len %d, got: %d", 0, b.Len)
}
if len(b.Vars) != 0 {
t.Errorf("after reset should have len(vars) == 0, got: %d", len(b.Vars))
}
if b.Prefix != "" {
t.Errorf("after reset should have empty prefix, got: %s", b.Prefix)
}
if b.quote != 0 || b.quoteDollarTag != "" || b.multilineComment || b.balanceCount != 0 {
t.Fatal("after reset should have a cleared parse state")
}
if st := b.State(); st != "=" {
t.Errorf("after reset should have state `=`, got: %q", st)
}
if b.ready {
t.Fatal("after reset should not be ready")
}
})
}
}
func TestEmptyVariablesRawString(t *testing.T) {
stmt := new(Stmt)
stmt.AppendString("select ", "\n")
stmt.Prefix = "SELECT"
v := &Var{
I: 7,
End: 9,
Name: "a",
Len: 0,
}
stmt.Vars = append(stmt.Vars, v)
if exp, got := "select ", stmt.RawString(); exp != got {
t.Fatalf("Defined=false, expected: %s, got: %s", exp, got)
}
v.Defined = true
if exp, got := "select :a", stmt.RawString(); exp != got {
t.Fatalf("Defined=true, expected: %s, got: %s", exp, got)
}
}
func TestVarSubstitute(t *testing.T) {
a512 := sl(512, 'a')
tests := []struct {
s string
v *Var
sub string
exp string
}{
{`:a`, v(0, `a`), `x`, `x`},
{` :a`, v(1, `a`), `x`, ` x`},
{`:a `, v(0, `a`), `x`, `x `},
{` :a `, v(1, `a`), `x`, ` x `},
{` :'a' `, v(1, `a`, `'`), `x`, ` 'x' `},
{` :"a" `, v(1, "a", `"`), `x`, ` "x" `},
{`:a`, v(0, `a`), ``, ``},
{` :a`, v(1, `a`), ``, ` `},
{`:a `, v(0, `a`), ``, ` `},
{` :a `, v(1, `a`), ``, ` `},
{` :'a' `, v(1, `a`, `'`), ``, ` '' `},
{` :"a" `, v(1, "a", `"`), "", ` "" `},
{` :aaa `, v(1, "aaa"), "", " "},
{` :aaa `, v(1, "aaa"), a512, " " + a512 + " "},
{` :` + a512 + ` `, v(1, a512), "", " "},
{`:foo`, v(0, "foo"), "这是一个", `这是一个`},
{`:foo `, v(0, "foo"), "这是一个", `这是一个 `},
{` :foo`, v(1, "foo"), "这是一个", ` 这是一个`},
{` :foo `, v(1, "foo"), "这是一个", ` 这是一个 `},
{`:'foo'`, v(0, `foo`, `'`), `这是一个`, `'这是一个'`},
{`:'foo' `, v(0, `foo`, `'`), `这是一个`, `'这是一个' `},
{` :'foo'`, v(1, `foo`, `'`), `这是一个`, ` '这是一个'`},
{` :'foo' `, v(1, `foo`, `'`), `这是一个`, ` '这是一个' `},
{`:"foo"`, v(0, `foo`, `"`), `这是一个`, `"这是一个"`},
{`:"foo" `, v(0, `foo`, `"`), `这是一个`, `"这是一个" `},
{` :"foo"`, v(1, `foo`, `"`), `这是一个`, ` "这是一个"`},
{` :"foo" `, v(1, `foo`, `"`), `这是一个`, ` "这是一个" `},
{`:型`, v(0, `型`), `x`, `x`},
{` :型`, v(1, `型`), `x`, ` x`},
{`:型 `, v(0, `型`), `x`, `x `},
{` :型 `, v(1, `型`), `x`, ` x `},
{` :'型' `, v(1, `型`, `'`), `x`, ` 'x' `},
{` :"型" `, v(1, "型", `"`), `x`, ` "x" `},
{`:型`, v(0, `型`), ``, ``},
{` :型`, v(1, `型`), ``, ` `},
{`:型 `, v(0, `型`), ``, ` `},
{` :型 `, v(1, `型`), ``, ` `},
{` :'型' `, v(1, `型`, `'`), ``, ` '' `},
{` :"型" `, v(1, "型", `"`), "", ` "" `},
{`:型示師`, v(0, `型示師`), `本門台初埼本門台初埼`, `本門台初埼本門台初埼`},
{` :型示師`, v(1, `型示師`), `本門台初埼本門台初埼`, ` 本門台初埼本門台初埼`},
{`:型示師 `, v(0, `型示師`), `本門台初埼本門台初埼`, `本門台初埼本門台初埼 `},
{` :型示師 `, v(1, `型示師`), `本門台初埼本門台初埼`, ` 本門台初埼本門台初埼 `},
{` :型示師 `, v(1, `型示師`), `本門台初埼本門台初埼`, ` 本門台初埼本門台初埼 `},
{` :'型示師' `, v(1, `型示師`, `'`), `a`, ` 'a' `},
{` :"型示師" `, v(1, `型示師`, `"`), `b`, ` "b" `},
{` :'型示師' `, v(1, `型示師`, `'`), `本門台初埼本門台初埼`, ` '本門台初埼本門台初埼' `},
{` :"型示師" `, v(1, `型示師`, `"`), `本門台初埼本門台初埼`, ` "本門台初埼本門台初埼" `},
{`:{?foo}`, v(0, `foo`, `?`), ``, `TRUE`},
{`:{?foo_}`, v(0, `foo_`, `?`), ``, `FALSE`},
{` :{?foo} `, v(1, `foo`, `?`), ``, ` TRUE `},
{` :{?foo_} `, v(1, `foo_`, `?`), ``, ` FALSE `},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Logf("`%s` `%s`: `%s` --> `%s`", test.v.Name, test.sub, test.s, test.exp)
t.Logf("[]byte: %d", len([]byte(test.s)))
t.Logf("[]rune: %d", len([]rune(test.s)))
t.Logf("v.I:%d v.End:%d", test.v.I, test.v.End)
r, n := test.v.Substitute([]rune(test.s), test.sub, test.v.Name == "foo")
if exp := len([]rune(test.exp)); n != exp {
t.Errorf("expected n to be %d, got: %d", exp, n)
}
if s := string(r); s != test.exp {
t.Errorf("expected %q, got: %q", test.exp, s)
}
})
}
}
func v(i int, name string, x ...string) *Var {
z := &Var{
I: i,
Name: name,
End: i + len([]rune(name)) + 1, // :name
}
if len(x) != 0 {
z.Quote = []rune(x[0])[0]
switch z.Quote {
case '\'', '"':
z.End += 2 // '', ""
case '?':
z.End += 3 // {?}
}
}
return z
}
// cc combines commands with params.
func cc(cmds []string, params []string) []string {
if len(cmds) == 0 {
return []string{"|"}
}
z := make([]string, len(cmds))
if len(cmds) != len(params) {
panic("length of params should be same as cmds")
}
for i := 0; i < len(cmds); i++ {
z[i] = cmds[i] + "|" + params[i]
}
return z
}
func sp(a, sep string) func() ([]rune, error) {
s := strings.Split(a, sep)
return func() ([]rune, error) {
if len(s) > 0 {
z := s[0]
s = s[1:]
return []rune(z), nil
}
return nil, io.EOF
}
}
func sl(n int, r rune) string {
z := make([]rune, n)
for i := 0; i < n; i++ {
z[i] = r
}
return string(z)
}
================================================
FILE: styles/styles.go
================================================
// Package styles provides chroma styles based on the chroma styles but removing
// the backgrounds.
package styles
import (
"sync"
"github.com/alecthomas/chroma/v2"
cstyles "github.com/alecthomas/chroma/v2/styles"
)
// styles is the set of styles with their background colors removed.
var styles = struct {
styles map[string]*chroma.Style
sync.Mutex
}{
styles: make(map[string]*chroma.Style),
}
// Get retrieves the equivalent chroma style.
func Get(name string) *chroma.Style {
styles.Lock()
defer styles.Unlock()
if _, ok := styles.styles[name]; !ok {
// get original style
s := cstyles.Get(name)
// create new entry map
m := make(chroma.StyleEntries)
for _, typ := range s.Types() {
// skip background
if typ == chroma.Background {
continue
}
z := s.Get(typ)
// unset background
z.Background = chroma.Colour(0)
m[typ] = z.String()
}
styles.styles[name] = chroma.MustNewStyle(s.Name, m)
}
return styles.styles[name]
}
================================================
FILE: testcli.go
================================================
//go:build ignore
// Command testcli runs goexpect tests against a built usql binary.
package main
import (
"bytes"
"context"
"flag"
"fmt"
"io"
"log"
"os"
"regexp"
"time"
gexpect "github.com/google/goexpect"
)
func main() {
binpath := flag.String("binpath", "./usql", "bin path")
deadline := flag.Duration("deadline", 5*time.Minute, "total execution deadline")
timeout := flag.Duration("timeout", 2*time.Minute, "individual test timeout")
re := flag.String("run", "", "test name regexp to run")
flag.Parse()
if err := run(context.Background(), *binpath, *deadline, *timeout, *re); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
func run(ctx context.Context, binpath string, deadline, timeout time.Duration, re string) error {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(deadline))
defer cancel()
tests, err := cliTests()
if err != nil {
return err
}
var nameRE *regexp.Regexp
if re != "" {
nameRE, err = regexp.Compile(re)
if err != nil {
return err
}
}
for _, test := range tests {
if nameRE != nil && !nameRE.MatchString(test.name) {
log.Printf(">>> SKIPPING: %s", test.name)
continue
}
log.Printf(">>> RUNNING: %s", test.name)
if err := test.do(ctx, binpath, timeout); err != nil {
return fmt.Errorf("test %s: %v", test.name, err)
}
log.Printf(">>> COMPLETED: %s", test.name)
}
return nil
}
type Test struct {
name string
script string
args []string
env []string
}
func cliTests() ([]Test, error) {
env := append(os.Environ(), "TERM=xterm-256color")
return []Test{
{
"complex/postgres",
"./contrib/postgres/test.sql",
[]string{"pgsql://postgres:P4ssw0rd@localhost", "--set=PAGER=''", "--pset=pager=off"},
env,
},
{
"complex/mysql",
"./contrib/mysql/test.sql",
[]string{"my://root:P4ssw0rd@localhost", "--set=PAGER=''", "--pset=pager=off"},
env,
},
{
"complex/sqlite3",
"./contrib/sqlite3/test.sql",
[]string{"sqlite:./testdata/sqlite3_test.db", "--set=PAGER=''", "--pset=pager=off"},
env,
},
{
"complex/moderncsqlite",
"./contrib/sqlite3/test.sql",
[]string{"mq:./testdata/moderncsqlite_test.db", "--set=PAGER=''", "--pset=pager=off"},
env,
},
{
"complex/sqlserver",
"./contrib/sqlserver/test.sql",
[]string{"sqlserver://sa:Adm1nP@ssw0rd@localhost/", "--set=PAGER=''", "--pset=pager=off"},
env,
},
{
"complex/cassandra",
"./contrib/cassandra/test.sql",
[]string{"ca://cassandra:cassandra@localhost", "--set=PAGER=''", "--pset=pager=off"},
env,
},
{
"copy/a_bit_of_everything",
"./testdata/copy.sql",
[]string{"--set=PAGER=''", "--pset=pager=off"},
env,
},
}, nil
}
func (test Test) do(ctx context.Context, binpath string, timeout time.Duration) error {
exp, errch, err := gexpect.SpawnWithArgs(
append([]string{binpath}, test.args...),
timeout,
gexpect.SetEnv(test.env),
gexpect.Tee(&noopWriteCloser{os.Stdout}),
)
if err != nil {
return err
}
buf, err := os.ReadFile(test.script)
if err != nil {
return err
}
for _, line := range bytes.Split(buf, []byte{'\n'}) {
if err := exp.Send(string(line) + "\n"); err != nil {
return err
}
}
select {
case <-ctx.Done():
defer exp.Close()
return ctx.Err()
case err := <-errch:
defer exp.Close()
return err
}
}
type noopWriteCloser struct {
io.Writer
}
func (*noopWriteCloser) Close() error {
return nil
}
================================================
FILE: testdata/copy.sql
================================================
\set PGDB pg://postgres:P4ssw0rd@localhost
\set MYDB my://root:P4ssw0rd@localhost
\set SQDB sq:./testdata/copy_test.db
\set MSDB ms://sa:Adm1nP@ssw0rd@localhost/
\connect :PGDB
drop table if exists a_bit_of_everything;
create table a_bit_of_everything (
a_id serial primary key,
a_blob bytea,
a_bool boolean,
a_date timestamp with time zone,
a_double double precision,
a_int integer,
a_text text
);
insert into a_bit_of_everything
(a_blob, a_bool, a_date, a_double, a_int, a_text)
values
(E'more\ntext'::bytea, true, now(), 32.0, 0, 'some text'),
(E'other\ntext'::bytea, false, now()+interval '3 days', 64.0, 128, 'foobar')
;
select * from a_bit_of_everything;
\connect :MYDB
drop database if exists testdb;
create database testdb;
use testdb;
drop table if exists a_bit_of_everything;
create table a_bit_of_everything (
a_id integer not null auto_increment primary key,
a_blob blob,
a_bool boolean,
a_date datetime,
a_double double,
a_int integer,
a_text text
);
\copy :PGDB :MYDB/testdb 'select * from a_bit_of_everything' 'a_bit_of_everything(a_id, a_blob, a_bool, a_date, a_double, a_int, a_text)'
\connect :MYDB/testdb
select * from a_bit_of_everything;
\! rm -f ./testdata/test3.db
\connect :SQDB
create table a_bit_of_everything (
a_id integer primary key autoincrement,
a_blob blob,
a_bool boolean,
a_date datetime,
a_double double precision,
a_int integer,
a_text text
);
\copy :PGDB :SQDB 'select * from a_bit_of_everything' 'a_bit_of_everything(a_id, a_blob, a_bool, a_date, a_double, a_int, a_text)'
\connect :SQDB
select * from a_bit_of_everything;
\connect :MSDB
drop table if exists a_bit_of_everything;
create table a_bit_of_everything (
-- a_id integer identity(1,1) primary key, -- doesn't work currently
a_id integer,
a_blob varbinary(max),
a_bool bit,
a_date datetime2,
a_double double precision,
a_int integer,
a_text text
);
\copy :PGDB :MSDB 'select * from a_bit_of_everything' 'a_bit_of_everything(a_id, a_blob, a_bool, a_date, a_double, a_int, a_text)'
\connect :MSDB
select * from a_bit_of_everything;
\quit
================================================
FILE: testdata/inc_test.sql
================================================
select 'testdata/inc_test.sql';
\i sub/inc_test2.sql
================================================
FILE: testdata/inc_test_z.sql
================================================
select 'testdata/inc_test_z.sql';
================================================
FILE: testdata/numbers.sql
================================================
select '1251258098.1555901285'::numeric;
select '1251258098.1555901285'::float4;
select '1251258098.1555901285'::float8;
select '1251258098.1555901285'::double precision;
================================================
FILE: testdata/quotes.sql
================================================
-- echo all
\set ECHO all
-- conditional variables display FALSE when name is not set
\unset foo
\echo :{?foo}
-- conditional variables display TRUE when name is set
\set foo 'bar'
\echo :{?foo}
-- single quoted strings will decode '' as ' and decode \n, \t, \b, \r, \f, \digits octals, \xdigits (standard escapes)
\set foo 'bar''bar\r\n'
-- single quoted variables escape ' but does not escape special characters
\echo :'foo'
-- double quoted variables do not escape ' or special characters
\echo :"foo"
-- single quoted strings decode any other standard escape (\) as literal
\set foo 'bar\'''bar'
\echo :foo
\echo :'foo'
-- single quoted variables escape \ with E'' style strings
\set foo 'bar\\\''
\echo :foo
\echo :'foo'
\echo :"foo"
-- backticks interpolate unquoted variables
\set foo 'bar'
\echo `echo :foo`
-- backticks interpolate single quoted variables
\echo `echo :'foo'`
-- backticks do not interpolate double quoted variables
\echo `echo :"foo"`
-- backticks have error messages for single quoted variables containing \r or \n when using :'' syntax
\set foo 'bar\r\n'
\echo `echo :'foo'`
================================================
FILE: testdata/sub/inc_test2.sql
================================================
select 'testdata/sub/inc_test2.sql';
select 'from testdata/sub/inc_test2.sql, doing: \i inc_test_z.sql';
\i inc_test_z.sql
select 'from testdata/sub/inc_test2.sql, doing: \ir inc_test_z.sql';
\ir inc_test_z.sql
================================================
FILE: testdata/sub/inc_test_z.sql
================================================
select 'testdata/sub/inc_test_z.sql'
================================================
FILE: text/errors.go
================================================
package text
import (
"errors"
)
var (
// ErrNotConnected is the not connected error.
ErrNotConnected = errors.New(`not connected`)
// ErrNoSuchFileOrDirectory is the no such file or directory error.
ErrNoSuchFileOrDirectory = errors.New(`no such file or directory`)
// ErrCannotIncludeDirectories is the cannot include directories error.
ErrCannotIncludeDirectories = errors.New(`cannot include directories`)
// ErrMissingDSN is the missing dsn error.
ErrMissingDSN = errors.New(`missing dsn`)
// ErrNoPreviousTransactionExists is the no previous transaction exists error.
ErrNoPreviousTransactionExists = errors.New(`no previous transaction exists`)
// ErrPreviousTransactionExists is the previous transaction exists error.
ErrPreviousTransactionExists = errors.New(`previous transaction exists`)
// ErrPasswordAttemptsExhausted is the exhausted password attempts error.
ErrPasswordAttemptsExhausted = errors.New(`password attempts exhausted`)
// ErrSingleTransactionCannotBeUsedWithInteractiveMode is the single transaction cannot be used with interactive mode error.
ErrSingleTransactionCannotBeUsedWithInteractiveMode = errors.New(`--single-transaction cannot be used with interactive mode`)
// ErrNoEditorDefined is the no editor defined error.
ErrNoEditorDefined = errors.New(`no editor defined`)
// ErrUnknownCommand is the unknown command error.
ErrUnknownCommand = errors.New(`unknown command`)
// ErrMissingRequiredArgument is the missing required argument error.
ErrMissingRequiredArgument = errors.New(`missing required argument`)
// ErrDriverNotAvailable is the driver not available error.
ErrDriverNotAvailable = errors.New(`driver not available`)
// ErrPasswordNotSupportedByDriver is the password not supported by driver error.
ErrPasswordNotSupportedByDriver = errors.New(`\password not supported by driver`)
// ErrUnterminatedQuotedString is the unterminated quoted string error.
ErrUnterminatedQuotedString = errors.New(`unterminated quoted string`)
// ErrNoShellAvailable is the no SHELL available error.
ErrNoShellAvailable = errors.New(`no SHELL available`)
// ErrNotInteractive is the not interactive error.
ErrNotInteractive = errors.New(`not interactive`)
// ErrInvalidType is the invalid type error.
ErrInvalidType = errors.New(`invalid -TYPE: TYPE must be password, string, int, uint, float, or bool`)
// ErrInvalidIdentifier is the invalid identifier error.
ErrInvalidIdentifier = errors.New(`invalid identifier`)
// ErrInvalidValue is the invalid value error.
ErrInvalidValue = errors.New(`invalid value`)
// ErrTooManyRows is the too many rows error.
ErrTooManyRows = errors.New(`too many rows`)
// ErrInvalidFormatType is the invalid format type error.
ErrInvalidFormatType = errors.New(`\pset: allowed formats are unaligned, aligned, wrapped, html, asciidoc, latex, latex-longtable, troff-ms, json, csv`)
// ErrInvalidFormatPagerType is the invalid format pager error.
ErrInvalidFormatPagerType = errors.New(`\pset: allowed pager values are on, off, always`)
// ErrInvalidFormatExpandedType is the invalid format expanded error.
ErrInvalidFormatExpandedType = errors.New(`\pset: allowed expanded values are on, off, auto`)
// ErrInvalidFormatLineStyle is the invalid format line style error.
ErrInvalidFormatLineStyle = errors.New(`\pset: allowed line styles are ascii, old-ascii, unicode`)
// ErrInvalidFormatBorderLineStyle is the invalid format border line style error.
ErrInvalidFormatBorderLineStyle = errors.New(`\pset: allowed Unicode border line styles are single, double`)
// ErrInvalidTimezoneLocation is the invalid timezone location error.
ErrInvalidTimezoneLocation = errors.New(`\pset: invalid timezone location`)
// ErrGraphicsNotSupported is the graphics not supported error.
ErrGraphicsNotSupported = errors.New(`\chart: graphics not supported in terminal`)
// ErrNoNumericColumns is the no numeric columns error.
ErrNoNumericColumns = errors.New(`\chart: no numeric columns found`)
// ErrInvalidQuotedString is the invalid quoted string error.
ErrInvalidQuotedString = errors.New(`invalid quoted string`)
// ErrInvalidFormatOption is the invalid format option error.
ErrInvalidFormatOption = errors.New(`invalid format option`)
// ErrInvalidWatchDuration is the invalid watch duration error.
ErrInvalidWatchDuration = errors.New(`invalid watch duration`)
// ErrUnableToNormalizeURL is the unable to normalize URL error.
ErrUnableToNormalizeURL = errors.New(`unable to normalize URL`)
// ErrInvalidIsolationLevel is the invalid isolation level error.
ErrInvalidIsolationLevel = errors.New(`invalid isolation level`)
// ErrNotSupported is the not supported error.
ErrNotSupported = errors.New(`not supported`)
// ErrWrongNumberOfArguments is the wrong number of arguments error.
ErrWrongNumberOfArguments = errors.New(`wrong number of arguments`)
// ErrUnknownFileType is the unknown file type error.
ErrUnknownFileType = errors.New(`unknown file type`)
// ErrNamedConnectionIsNotAURL is the named connection is not a url error.
ErrNamedConnectionIsNotAURL = errors.New(`named connection is not a url`)
// ErrInvalidConfig is the invalid config error.
ErrInvalidConfig = errors.New(`invalid config`)
// ErrIfEscaped is the if escaped error.
ErrIfEscaped = errors.New(`\if escaped`)
// ErrEndIfNoMatchingIf is the endif no matching if error.
ErrEndIfNoMatchingIf = errors.New(`\endif: no matching \if`)
)
================================================
FILE: text/license.go
================================================
package text
// Code generated by gen.go. DO NOT EDIT.
// License contains the license text for usql.
const License = `The MIT License (MIT)
Copyright (c) 2015-2025 Kenneth Shaw
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.`
================================================
FILE: text/text.go
================================================
// Package text contains the text (and eventually translations) for the usql
// application.
package text
import (
"bytes"
_ "embed"
"image"
"image/png"
"io"
"regexp"
"strings"
)
// Various usql text bits.
var (
CommandName = `usql`
CommandVersion = `0.0.0-dev`
PassfileName = CommandName + `pass`
ConfigName = "config"
Banner = `the universal command-line interface for SQL databases`
CommandHelpHint = `hint: try "` + CommandName + ` --help" for more information.`
GoInstallHint = "\ntry:\n\n go install -tags 'most %s' github.com/xo/usql@%s\n\n"
NotConnected = `(not connected)`
HelpPrefix = `help`
QuitPrefix = `quit`
ExitPrefix = `exit`
WelcomeDesc = `Type "` + HelpPrefix + `" for help.`
QueryBufferEmpty = `Query buffer is empty.`
QueryBufferReset = `Query buffer reset (cleared).`
InvalidCommand = `Invalid command \%s. Try \? for help.`
ExtraArgumentIgnored = `\%s: extra argument %q ignored`
MissingRequiredArg = `\%s: missing required argument`
Copyright = CommandName + ", " + Banner + ".\n\n" + License
RowCount = `(%d rows)`
AvailableDrivers = `Available Drivers:`
ConnInfo = `Connected with driver %s (%s)`
EnterPassword = `Enter password: `
EnterPreviousPassword = `Enter previous password: `
PasswordsDoNotMatch = `Passwords do not match, trying again ...`
NewPassword = `Enter new password: `
ConfirmPassword = `Confirm password: `
PasswordChangeFailed = `\password for %q failed: %v`
CouldNotSetVariable = `could not set variable %q`
ChartParseFailed = `\chart: invalid argument for %q: %v`
CommandIgnoredUseEndIf = `%s command ignored; use \endif or %s to exit current \if block`
UnrecognizedValueForCond = `unrecognized value %q for "\%s expression": Boolean expected`
// PasswordChangeSucceeded = `\password succeeded for %q`
HelpDesc string
HelpDescShort = `Use \? for help or press control-C to clear the input buffer.`
HelpBanner = `You are using ` + CommandName + ", " + Banner + `.`
HelpCommandPrefix = `Type: `
HelpCommands = [][]string{
{`copyright`, `for distribution terms`},
//{`h`, `for help with SQL commands`},
{`?`, `for help with ` + CommandName + ` commands`},
{`g`, `or terminate with semicolon to execute query`},
{`q`, `to quit`},
}
QuitDesc = `Use \q to quit.`
UnknownFormatFieldName = `unknown option: %s`
FormatFieldInvalid = `unrecognized value %q for "%s"`
FormatFieldInvalidValue = `unrecognized value %q for "%s": %s expected`
FormatFieldNameSetMap = map[string]string{
`border`: `Border style is %d.`,
`columns`: `Target width is %d.`,
`expanded`: `Expanded display is %s.`,
`expanded_auto`: `Expanded display is used automatically.`,
`fieldsep`: `Field separator is %q.`,
`fieldsep_zero`: `Field separator is zero byte.`,
`footer`: `Default footer is %s.`,
`format`: `Output format is %s.`,
`linestyle`: `Line style is %s.`,
`locale`: `Locale is %q.`,
`null`: `Null display is %q.`,
`numericlocale`: `Locale-adjusted numeric output is %s.`,
`pager`: `Pager usage is %s.`,
`pager_min_lines`: `Pager won't be used for less than %d line(s).`,
`recordsep`: `Field separator is %q.`,
`recordsep_zero`: `Record separator is zero byte.`,
`tableattr`: `Table attributes are %q.`,
`time`: `Time display is %s.`,
`title`: `Title is %q.`,
`tuples_only`: `Tuples only is %s.`,
`unicode_border_linestyle`: `Unicode border line style is %q.`,
`unicode_column_linestyle`: `Unicode column line style is %q.`,
`unicode_header_linestyle`: `Unicode header line style is %q.`,
}
FormatFieldNameUnsetMap = map[string]string{
`tableattr`: `Table attributes unset.`,
`title`: `Title is unset.`,
}
TimingSet = `Timing is %s.`
TimingDesc = `Time: %0.3f ms`
InvalidValue = `invalid -%s value %q: %s`
NotSupportedByDriver = `%s not supported by %s driver`
RelationNotFound = `Did not find any relation named "%s".`
InvalidOption = `invalid option %q`
NotificationReceived = `Asynchronous notification %q %sreceived from server process with PID %d.`
NotificationPayload = `with payload %q `
UnknownShortAlias = `(unk)`
InvalidNamedConnection = `warning: named connection %q was not defined: %v`
ChartsPathDoesNotExist = `warning: charts_path %q does not exist`
ChartsPathIsNotADirectory = `warning: charts_path %q is not a directory`
UsageTemplate = `Usage:
{{.UseLine}}
Arguments:
DSN database url or connection name
Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}
`
ChartUsage = `\chart: create and display charts from SQL data
usage: \chart [opts]
available options:
help
title [title] chart title
subtitle [subtitle] chart subtitle
size NxN chart size (width x height)
bg [color] chart background color
type [bar|line] chart type
prec [num] data decimal precision
file [path] write chart to file (svg)`
)
func init() {
// setup help description
cmds := make([]string, len(HelpCommands))
for i, h := range HelpCommands {
cmds[i] = `\` + h[0] + " " + h[1]
}
HelpDesc = HelpBanner +
"\n" + HelpCommandPrefix +
strings.Join(cmds, "\n"+strings.Repeat(" ", len(HelpCommandPrefix)))
}
var spaceRE = regexp.MustCompile(`\s+`)
// Command returns the command name without spaces.
var Command = func() string {
return spaceRE.ReplaceAllString(CommandName, "")
}
// CommandLower returns the lower case command name without spaces.
var CommandLower = func() string {
return strings.ToLower(Command())
}
// CommandUpper returns the upper case command name without spaces.
var CommandUpper = func() string {
return strings.ToUpper(Command())
}
// Short returns the command name and banner.
var Short = func() string {
return Command() + ", " + Banner
}
// UsageString is used to return the
var UsageString = func() string {
return ""
}
// Usage displays writes the command line options to w, optionally including a
// banner.
func Usage(w io.Writer, banner bool) {
if banner {
_, _ = w.Write([]byte(Short() + "\n\n"))
}
_, _ = w.Write([]byte(UsageString()))
}
// Logo is the logo.
var Logo image.Image
// LogoPng is the embedded logo.
//
//go:embed logo.png
var LogoPng []byte
func init() {
var err error
if Logo, err = png.Decode(bytes.NewReader(LogoPng)); err != nil {
panic(err)
}
}
================================================
FILE: update-deps.sh
================================================
#!/bin/bash
SRC=$(realpath $(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd))
set -e
pushd $SRC &> /dev/null
(set -x;
go get -u -v -x $@ $(go list -tags 'all test' -f '{{ join .Imports "\n" }}' ./internal/...)
)
PKGS=$(go list -tags 'all test' -f '{{ join .Imports "\n" }}'|grep 'github.com/xo/usql'|grep -v drivers|grep -v internal)
(set -x;
go get -u -v -x $@ $PKGS
)
(set -x;
go mod tidy
)
popd &> /dev/null