From 652ffbcb3172017b0d68db107555ffd9f96e52b3 Mon Sep 17 00:00:00 2001 From: mii9000 Date: Sun, 3 Mar 2024 09:54:44 +0600 Subject: [PATCH 01/17] adds support for BigQuery --- docker-compose.yml | 9 + go.mod | 42 + go.sum | 853 ++++++++++++++++++++ main.go | 1 + pkg/driver/bigquery/bigquery.go | 442 ++++++++++ pkg/driver/bigquery/bigquery_client.go | 84 ++ pkg/driver/bigquery/bigquery_client_test.go | 88 ++ pkg/driver/bigquery/bigquery_test.go | 255 ++++++ pkg/driver/bigquery/test_cred.json | 13 + 9 files changed, 1787 insertions(+) create mode 100644 pkg/driver/bigquery/bigquery.go create mode 100644 pkg/driver/bigquery/bigquery_client.go create mode 100644 pkg/driver/bigquery/bigquery_client_test.go create mode 100644 pkg/driver/bigquery/bigquery_test.go create mode 100644 pkg/driver/bigquery/test_cred.json diff --git a/docker-compose.yml b/docker-compose.yml index eba59e5b..5e19483c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,7 @@ services: - clickhouse - clickhouse-cluster-01 - clickhouse-cluster-02 + - bigquery environment: CLICKHOUSE_TEST_URL: clickhouse://clickhouse:9000/dbmate_test CLICKHOUSE_CLUSTER_01_TEST_URL: clickhouse://ch-cluster-01:9000/dbmate_test @@ -19,6 +20,7 @@ services: MYSQL_TEST_URL: mysql://root:root@mysql/dbmate_test POSTGRES_TEST_URL: postgres://postgres:postgres@postgres/dbmate_test?sslmode=disable SQLITE_TEST_URL: sqlite3:/tmp/dbmate_test.sqlite3 + BIGQUERY_TEST_URL: bigquery://test/dbmate_test?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true dbmate: build: @@ -63,3 +65,10 @@ services: - zookeeper volumes: - ./pkg/driver/clickhouse/testdata/cluster_config/ch-cluster-02:/etc/clickhouse-server + + bigquery: + image: ghcr.io/goccy/bigquery-emulator:0.4.4 + ports: + - "9050:9050" + command: | + --project=test --dataset=dbmate_test diff --git a/go.mod b/go.mod index 2349dcba..952923ed 100644 --- a/go.mod +++ b/go.mod @@ -16,14 +16,36 @@ require ( ) require ( + cloud.google.com/go v0.110.4 // indirect + cloud.google.com/go/bigquery v1.52.0 // indirect + cloud.google.com/go/compute v1.21.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.1.1 // indirect github.com/ClickHouse/ch-go v0.61.2 // indirect github.com/andybalholm/brotli v1.1.0 // indirect + github.com/apache/arrow/go/v12 v12.0.0 // indirect + github.com/apache/thrift v0.16.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.7.1 // indirect + github.com/goccy/go-json v0.9.11 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/flatbuffers v2.0.8+incompatible // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/s2a-go v0.1.4 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect + github.com/googleapis/gax-go/v2 v2.11.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/klauspost/asmfmt v1.3.2 // indirect github.com/klauspost/compress v1.17.6 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect + github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect github.com/paulmach/orb v0.11.1 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -31,9 +53,29 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect + go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel v1.23.1 // indirect go.opentelemetry.io/otel/trace v1.23.1 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/mod v0.13.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/oauth2 v0.10.0 // indirect + golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.14.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + google.golang.org/api v0.126.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/grpc v1.58.3 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/bigquery v1.2.0 // indirect + gorm.io/gorm v1.24.0 // indirect ) diff --git a/go.sum b/go.sum index ad38fd13..51f99085 100644 --- a/go.sum +++ b/go.sum @@ -1,36 +1,366 @@ +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.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= +cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +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/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.52.0 h1:JKLNdxI0N+TIUWD6t9KN646X27N5dQWq9dZbbTWZ8hc= +cloud.google.com/go/bigquery v1.52.0/go.mod h1:3b/iXjRQGU4nKa87cXeg6/gogLjO8C6PmuM8i5Bi/u4= +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 v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk= +cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +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/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.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= +cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +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/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/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/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/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= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ClickHouse/ch-go v0.61.2 h1:8+8eKO2VgxoRa0yLJpWwkqJxi/jrtP5Z+J6eZdPfwdc= github.com/ClickHouse/ch-go v0.61.2/go.mod h1:ZSVIE1A7mGJNcJeBvVF1v5bo12n0Wmnw30RhnPCpLzg= github.com/ClickHouse/clickhouse-go/v2 v2.18.0 h1:O1LicIeg2JS2V29fKRH4+yT3f6jvvcJBm506dpVQ4mQ= github.com/ClickHouse/clickhouse-go/v2 v2.18.0/go.mod h1:ztQvX6wm7kAbhJslS87EXEhOVNY/TObXwyURnGju5FQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/v12 v12.0.0 h1:xtZE63VWl7qLdB0JObIXvvhGjoVNrQ9ciIHG2OK5cmc= +github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg= +github.com/apache/thrift v0.16.0 h1:qEy6UW60iVOlUy+b9ZR0d5WzUWYGOo4HfopoyBaNmoY= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= +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/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/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +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/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 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-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-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +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 h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +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/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.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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 v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/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 v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +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.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +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.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= +github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +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/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/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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +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/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/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.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -41,7 +371,12 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +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/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= @@ -51,6 +386,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +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.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -59,8 +397,20 @@ github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/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/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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= @@ -72,57 +422,560 @@ github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgk github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI= github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= 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/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04 h1:qXafrlZL1WsJW5OokjraLLRURHiw0OzKHD/RNdspp4w= github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04/go.mod h1:FiwNQxz6hGoNFBC4nIx+CxZhI3nne5RmIOlT/MXcSD4= 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/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 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-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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +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/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.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +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.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= +golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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-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-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-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +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/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-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-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-20191113191852-77e3bb0ad9e7/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.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 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 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= +google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +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 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +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-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +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.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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-20200227125254-8fa46927fb4f/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/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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/bigquery v1.2.0 h1:E94oEXErYb4uImcR8oiCjE1SP2VdnrL5f3d78PtFWNk= +gorm.io/driver/bigquery v1.2.0/go.mod h1:/5kcyb6RVIk/seff6YANAjB5aisE4oqY35x0Ix9iwXY= +gorm.io/gorm v1.24.0 h1:j/CoiSm6xpRpmzbFJsQHYj+I8bGYWLXVHeYEyyKlF74= +gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +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= +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= diff --git a/main.go b/main.go index a1c920f0..aa5f2f31 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "github.com/urfave/cli/v2" "github.com/amacneil/dbmate/v2/pkg/dbmate" + _ "github.com/amacneil/dbmate/v2/pkg/driver/bigquery" _ "github.com/amacneil/dbmate/v2/pkg/driver/clickhouse" _ "github.com/amacneil/dbmate/v2/pkg/driver/mysql" _ "github.com/amacneil/dbmate/v2/pkg/driver/postgres" diff --git a/pkg/driver/bigquery/bigquery.go b/pkg/driver/bigquery/bigquery.go new file mode 100644 index 00000000..42832079 --- /dev/null +++ b/pkg/driver/bigquery/bigquery.go @@ -0,0 +1,442 @@ +package bigquery + +import ( + "context" + "database/sql" + "fmt" + "io" + "net/url" + "strings" + + "cloud.google.com/go/bigquery" + "github.com/amacneil/dbmate/v2/pkg/dbmate" + "github.com/amacneil/dbmate/v2/pkg/dbutil" + "google.golang.org/api/googleapi" + "google.golang.org/api/iterator" + _ "gorm.io/driver/bigquery" +) + +func init() { + dbmate.RegisterDriver(NewDriver, "bigquery") +} + +func NewDriver(config dbmate.DriverConfig) dbmate.Driver { + u := config.DatabaseURL + + return &Driver{ + migrationsTableName: config.MigrationsTableName, + datasetID: strings.TrimPrefix(u.Path, "/"), + projectID: u.Host, + log: config.Log, + databaseURL: u, + } +} + +// Helper function that accepts an operation function and executes it with a BigQuery client. +func (drv *Driver) withBigQueryClient(operation func(context.Context, *bigquery.Client) (bool, error)) (bool, error) { + ctx := context.Background() + + // Create a BigQuery client. + client, err := GetClient(ctx, drv.databaseURL.String()) + if err != nil { + return false, err + } + defer client.Close() + + // Execute the operation function with the client. + result, err := operation(ctx, client) + + if err != nil { + return result, err + } + + return result, nil +} + +// Helper function to check whether a table exists or not in a dataset +func tableExists(client *bigquery.Client, ctx context.Context, datasetID, tableName string) (bool, error) { + table := client.Dataset(datasetID).Table(tableName) + _, err := table.Metadata(ctx) + if err == nil { + return true, nil + } + if gError, ok := err.(*googleapi.Error); ok && gError.Code == 404 { + return false, nil + } + return false, err +} + +type Driver struct { + migrationsTableName string + datasetID string + projectID string + log io.Writer + databaseURL *url.URL +} + +func (drv *Driver) CreateDatabase() error { + exists, err := drv.DatabaseExists() + if err != nil { + return err + } + + createDataset := func(ctx context.Context, client *bigquery.Client) (bool, error) { + err := client.Dataset(drv.datasetID).Create(ctx, &bigquery.DatasetMetadata{}) + if err != nil { + return false, err + } + return true, nil + } + + if !exists { + _, err := drv.withBigQueryClient(createDataset) + if err != nil { + return err + } + } + + return nil +} + +func (drv *Driver) CreateMigrationsTable(db *sql.DB) error { + tableExists := func(ctx context.Context, client *bigquery.Client) (bool, error) { + return tableExists(client, ctx, drv.datasetID, drv.migrationsTableName) + } + + createTable := func(ctx context.Context, client *bigquery.Client) (bool, error) { + table := client.Dataset(drv.datasetID).Table(drv.migrationsTableName) + err := table.Create(ctx, &bigquery.TableMetadata{ + Schema: bigquery.Schema{ + {Name: "version", Type: bigquery.StringFieldType}, + }, + }) + if err != nil { + return false, err + } + + return true, nil + } + + exists, err := drv.withBigQueryClient(tableExists) + if err != nil { + return err + } + + if !exists { + _, err := drv.withBigQueryClient(createTable) + if err != nil { + return err + } + } + + return nil +} + +func (drv *Driver) DatabaseExists() (bool, error) { + datasetExists := func(ctx context.Context, client *bigquery.Client) (bool, error) { + it := client.Datasets(ctx) + for { + dataset, err := it.Next() + if err == iterator.Done { + break + } + if err != nil { + return false, err + } + if dataset.DatasetID == drv.datasetID { + return true, nil + } + } + return false, nil + } + + exists, err := drv.withBigQueryClient(datasetExists) + if err != nil { + return false, err + } + + return exists, nil +} + +func (drv *Driver) DropDatabase() error { + datasetDrop := func(ctx context.Context, client *bigquery.Client) (bool, error) { + err := client.Dataset(drv.datasetID).DeleteWithContents(ctx) + if err != nil { + return false, err + } + return true, nil + } + + exists, err := drv.DatabaseExists() + if err != nil { + return err + } + + if exists { + _, err = drv.withBigQueryClient(datasetDrop) + if err != nil { + return err + } + } + + return nil +} + +func generateDDL(db *sql.DB, projectID, datasetID, objectName, objectType string) (string, error) { + var ddl string + + // Query to retrieve object information + switch objectType { + case "BASE TABLE": + // Query to retrieve column information for tables + query := fmt.Sprintf(` + SELECT + column_name, + data_type, + is_nullable + FROM + `+"`%s.%s.INFORMATION_SCHEMA.COLUMNS`"+` + WHERE + table_name = '%s'`, projectID, datasetID, objectName) + + // Execute the query + rows, err := db.Query(query) + if err != nil { + return "", err + } + defer dbutil.MustClose(rows) + + // Generate DDL for tables + for rows.Next() { + var columnName, dataType, isNullable string + if err := rows.Scan(&columnName, &dataType, &isNullable); err != nil { + return "", err + } + if isNullable == "YES" { + ddl += fmt.Sprintf("\t%s %s,\n", columnName, dataType) + } else { + ddl += fmt.Sprintf("\t%s %s %s,\n", columnName, dataType, "NOT NULL") + } + } + if ddl != "" { + ddl = fmt.Sprintf("CREATE TABLE `%s.%s.%s` (\n%s);", projectID, datasetID, objectName, ddl) + } + case "VIEW": + // Query to retrieve view definition + query := fmt.Sprintf(` + SELECT + view_definition + FROM + `+"`%s.%s.INFORMATION_SCHEMA.VIEWS`"+` + WHERE + table_name = '%s'`, projectID, datasetID, objectName) + + // Execute the query + row := db.QueryRow(query) + if err := row.Scan(&ddl); err != nil { + return "", err + } + ddl = fmt.Sprintf("CREATE VIEW `%s.%s.%s` AS\n%s;", projectID, datasetID, objectName, ddl) + ddl = strings.ReplaceAll(ddl, "\n", "\n\t") + case "FUNCTION": + // Query to retrieve function definition + definitionQuery := fmt.Sprintf(` + SELECT + routine_definition + FROM + `+"`%s.%s.INFORMATION_SCHEMA.ROUTINES`"+` + WHERE + routine_name = '%s'`, projectID, datasetID, objectName) + + // Execute the query to fetch function definition + definitionRow := db.QueryRow(definitionQuery) + if err := definitionRow.Scan(&ddl); err != nil { + return "", err + } + + // Query to retrieve function parameters + paramQuery := fmt.Sprintf(` + SELECT + parameter_name, + data_type, + ordinal_position + FROM + `+"`%s.%s.INFORMATION_SCHEMA.PARAMETERS`"+` + WHERE + specific_name = '%s'`, projectID, datasetID, objectName) + + // Execute the query to fetch function parameters + paramRows, err := db.Query(paramQuery) + if err != nil { + return "", err + } + defer dbutil.MustClose(paramRows) + + // Construct function parameters list + var paramList []string + var returnType string + for paramRows.Next() { + var paramName sql.NullString + var dataType string + var ordinalPosition int + if err := paramRows.Scan(¶mName, &dataType, &ordinalPosition); err != nil { + return "", err + } + if ordinalPosition == 0 { + returnType = dataType + } else { + paramList = append(paramList, fmt.Sprintf("%s %s", paramName.String, dataType)) + } + } + params := strings.Join(paramList, ", ") + + // Construct the function DDL with parameters + ddl = fmt.Sprintf("CREATE FUNCTION `%s.%s.%s` (%s) RETURNS %s AS (\n\t%s\n);", projectID, datasetID, objectName, params, returnType, ddl) + default: + return "", fmt.Errorf("unsupported object type: %s", objectType) + } + + return ddl, nil +} + +func (drv *Driver) DumpSchema(db *sql.DB) ([]byte, error) { + var script []byte + + query := fmt.Sprintf(` + SELECT + table_name, + table_type, + 1 AS order_type + FROM + `+"`%s.%s.INFORMATION_SCHEMA.TABLES`"+` + WHERE + table_type = 'BASE TABLE' + UNION ALL + SELECT + table_name, + table_type, + 2 AS order_type + FROM + `+"`%s.%s.INFORMATION_SCHEMA.TABLES`"+` + WHERE + table_type = 'VIEW' + UNION ALL + SELECT + routine_name AS table_name, + 'FUNCTION' AS table_type, + 3 AS order_type + FROM + `+"`%s.%s.INFORMATION_SCHEMA.ROUTINES`"+` + ORDER BY + order_type;`, drv.projectID, drv.datasetID, drv.projectID, drv.datasetID, drv.projectID, drv.datasetID) + + // Execute the query + rows, err := db.Query(query) + if err != nil { + return nil, fmt.Errorf("error querying objects: %v", err) + } + defer dbutil.MustClose(rows) + + // Iterate over the results and generate DDL for each object + for rows.Next() { + var objectName, objectType string + var orderType int + if err := rows.Scan(&objectName, &objectType, &orderType); err != nil { + return nil, fmt.Errorf("error scanning object: %v", err) + } + + // Generate DDL for the object + ddl, err := generateDDL(db, drv.projectID, drv.datasetID, objectName, objectType) + if err != nil { + return nil, fmt.Errorf("error generating DDL for %s %s: %v", objectName, objectType, err) + } + + // Append the DDL to the script + script = append(script, []byte(ddl)...) + script = append(script, []byte("\n\n")...) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("error iterating objects: %v", err) + } + + return script, nil +} + +func (drv *Driver) MigrationsTableExists(db *sql.DB) (bool, error) { + tableExists := func(ctx context.Context, client *bigquery.Client) (bool, error) { + return tableExists(client, ctx, drv.datasetID, drv.migrationsTableName) + } + + exists, err := drv.withBigQueryClient(tableExists) + if err != nil { + return exists, err + } + + return exists, nil +} + +func (drv *Driver) DeleteMigration(db dbutil.Transaction, version string) error { + query := fmt.Sprintf("DELETE FROM %s.%s WHERE version = '%s';", drv.datasetID, drv.migrationsTableName, version) + _, err := db.Exec(query) + + return err +} + +func (drv *Driver) InsertMigration(db dbutil.Transaction, version string) error { + queryTemplate := `INSERT INTO %s.%s (version) VALUES ('%s');` + queryString := fmt.Sprintf(queryTemplate, drv.datasetID, drv.migrationsTableName, version) + + _, err := db.Exec(queryString, version) + return err +} + +func (drv *Driver) Open() (*sql.DB, error) { + con, err := sql.Open("bigquery", drv.databaseURL.String()) + return con, err +} + +func (drv *Driver) Ping() error { + db, err := drv.Open() + if err != nil { + return err + } + defer dbutil.MustClose(db) + + err = db.Ping() + + return err +} + +func (*Driver) QueryError(query string, err error) error { + return &dbmate.QueryError{Err: err, Query: query} +} + +func (drv *Driver) SelectMigrations(db *sql.DB, limit int) (map[string]bool, error) { + query := fmt.Sprintf("SELECT version FROM %s.%s ORDER BY version DESC", drv.datasetID, drv.migrationsTableName) + if limit >= 0 { + query = fmt.Sprintf("%s limit %d", query, limit) + } + rows, err := db.Query(query) + if err != nil { + return nil, err + } + + defer dbutil.MustClose(rows) + + migrations := map[string]bool{} + for rows.Next() { + var version string + if err := rows.Scan(&version); err != nil { + return nil, err + } + + migrations[version] = true + } + + if err = rows.Err(); err != nil { + return nil, err + } + + return migrations, nil +} diff --git a/pkg/driver/bigquery/bigquery_client.go b/pkg/driver/bigquery/bigquery_client.go new file mode 100644 index 00000000..0d8d3674 --- /dev/null +++ b/pkg/driver/bigquery/bigquery_client.go @@ -0,0 +1,84 @@ +package bigquery + +import ( + "context" + "fmt" + "net/url" + "strings" + + "cloud.google.com/go/bigquery" + "google.golang.org/api/option" +) + +type bigQueryConfig struct { + projectID string + location string + dataSet string + scopes []string + endpoint string + disableAuth bool + credentialFile string +} + +func GetClient(ctx context.Context, uri string) (*bigquery.Client, error) { + config, err := configFromUri(uri) + if err != nil { + return nil, err + } + + opts := []option.ClientOption{option.WithScopes(config.scopes...)} + if config.endpoint != "" { + opts = append(opts, option.WithEndpoint(config.endpoint)) + } + if config.disableAuth { + opts = append(opts, option.WithoutAuthentication()) + } + if config.credentialFile != "" { + opts = append(opts, option.WithCredentialsFile(config.credentialFile)) + } + + client, err := bigquery.NewClient(ctx, config.projectID, opts...) + if err != nil { + return nil, err + } + + return client, nil +} + +func configFromUri(uri string) (*bigQueryConfig, error) { + u, err := url.Parse(uri) + if err != nil { + return nil, invalidConnectionStringError(uri) + } + + if u.Scheme != "bigquery" { + return nil, fmt.Errorf("invalid prefix, expected bigquery:// got: %s", uri) + } + + if u.Path == "" { + return nil, invalidConnectionStringError(uri) + } + + fields := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") + if len(fields) > 2 { + return nil, invalidConnectionStringError(uri) + } + + config := &bigQueryConfig{ + projectID: u.Hostname(), + dataSet: fields[len(fields)-1], + endpoint: u.Query().Get("endpoint"), + disableAuth: u.Query().Get("disable_auth") == "true", + credentialFile: u.Query().Get("credential_file"), + } + + if len(fields) == 2 { + config.location = fields[0] + } + + return config, nil +} + +func invalidConnectionStringError(uri string) error { + return fmt.Errorf("invalid connection string: %s", uri) +} diff --git a/pkg/driver/bigquery/bigquery_client_test.go b/pkg/driver/bigquery/bigquery_client_test.go new file mode 100644 index 00000000..6b3d78fd --- /dev/null +++ b/pkg/driver/bigquery/bigquery_client_test.go @@ -0,0 +1,88 @@ +package bigquery + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func getCredFile(t *testing.T) string { + // Get the current working directory + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get current working directory: %v", err) + } + + // Construct the absolute path to the credential file + return filepath.Join(cwd, "test_cred.json") +} + +func assert(cases []string, t *testing.T, isValid bool) { + for _, c := range cases { + t.Run(c, func(t *testing.T) { + ctx := context.Background() + _, err := GetClient(ctx, c) + if isValid { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + } else { + require.Error(t, err) + } + }) + } +} + +func TestValidURIsWithoutCred(t *testing.T) { + cases := [4]string{ + "bigquery://projectid/dataset?disable_auth=true", + "bigquery://projectid/location/dataset?disable_auth=true", + "bigquery://projectid/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true", + "bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true", + } + + assert(cases[:], t, true) +} + +func TestValidURIsWithCred(t *testing.T) { + credentialFile := getCredFile(t) + + cases := [4]string{ + "bigquery://projectid/dataset?credential_file=" + credentialFile, + "bigquery://projectid/location/dataset?credential_file=" + credentialFile, + "bigquery://projectid/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&credential_file=" + credentialFile, + "bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&credential_file=" + credentialFile, + } + + assert(cases[:], t, true) +} + +func TestValidURIsWithEnvCred(t *testing.T) { + cases := [4]string{ + "bigquery://projectid/dataset", + "bigquery://projectid/location/dataset", + "bigquery://projectid/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050", + "bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050", + } + + env := "GOOGLE_APPLICATION_CREDENTIALS" + os.Setenv(env, getCredFile(t)) + defer os.Unsetenv(env) + + assert(cases[:], t, true) +} + +func TestInvalidURIs(t *testing.T) { + cases := [5]string{ + "bigquery://projectiddataset", + "bigquery://projectid/location/dataset/unknown", + "bigquery://projectid/dataset?endpoint=https://localhost:3000", + "bigquery://projectid/location/dataset?", + "bigquery://projectid/dataset?credential_file=does/not/exist", + } + + assert(cases[:], t, false) +} diff --git a/pkg/driver/bigquery/bigquery_test.go b/pkg/driver/bigquery/bigquery_test.go new file mode 100644 index 00000000..3ff37263 --- /dev/null +++ b/pkg/driver/bigquery/bigquery_test.go @@ -0,0 +1,255 @@ +package bigquery + +import ( + "database/sql" + "net/url" + "os" + "testing" + + "github.com/amacneil/dbmate/v2/pkg/dbmate" + "github.com/amacneil/dbmate/v2/pkg/dbutil" + "github.com/stretchr/testify/require" +) + +func getUrl() string { + return os.Getenv("BIGQUERY_TEST_URL") +} + +func getDbConnection(url string) (*sql.DB, error) { + return sql.Open("bigquery", url) +} + +func testBigqueryDriver(t *testing.T) *Driver { + u := dbutil.MustParseURL(getUrl()) + drv, err := dbmate.New(u).Driver() + require.NoError(t, err) + + return drv.(*Driver) +} + +func prepTestDB(t *testing.T) *sql.DB { + drv := testBigqueryDriver(t) + + // drop any existing database + err := drv.DropDatabase() + require.NoError(t, err) + + // create database + err = drv.CreateDatabase() + require.NoError(t, err) + + // connect database + db, err := drv.Open() + require.NoError(t, err) + + return db +} + +func TestGetDriver(t *testing.T) { + db := dbmate.New(dbutil.MustParseURL(getUrl())) + drvInterface, err := db.Driver() + require.NoError(t, err) + + // driver should have URL and default migrations table set + drv, ok := drvInterface.(*Driver) + require.True(t, ok) + require.Equal(t, db.DatabaseURL.String(), drv.databaseURL.String()) + require.Equal(t, "schema_migrations", drv.migrationsTableName) +} + +func TestConnectionString(t *testing.T) { + cases := [4]string{ + "bigquery://projectid/dataset", + "bigquery://projectid/location/dataset", + "bigquery://projectid/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050", + "bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true", + } + + for _, c := range cases { + t.Run(c, func(t *testing.T) { + _, err := url.Parse(c) + require.NoError(t, err) + }) + } +} + +func TestMySQLCreateDropDatabase(t *testing.T) { + drv := testBigqueryDriver(t) + + // drop any existing database + err := drv.DropDatabase() + require.NoError(t, err) + + // create database + err = drv.CreateDatabase() + require.NoError(t, err) + + // check that database exists and we can connect to it + func() { + db, err := drv.Open() + require.NoError(t, err) + defer dbutil.MustClose(db) + + err = db.Ping() + require.NoError(t, err) + }() + + // drop the database + err = drv.DropDatabase() + require.NoError(t, err) + + // check that database no longer exists + func() { + db, err := drv.Open() + require.NoError(t, err) + defer dbutil.MustClose(db) + + err = db.Ping() + require.Error(t, err) + require.Regexp(t, "dataset dbmate_test is not found", err.Error()) + }() +} + +func TestBigqueryCreateAndInsertMigration(t *testing.T) { + drv := testBigqueryDriver(t) + drv.migrationsTableName = "test_migrations" + + // prepare database + db := prepTestDB(t) + defer dbutil.MustClose(db) + + // create migrations table + err := drv.CreateMigrationsTable(db) + require.NoError(t, err) + + // insert migration + err = drv.InsertMigration(db, "abc1") + require.NoError(t, err) + err = drv.InsertMigration(db, "abc2") + require.NoError(t, err) +} + +func TestMySQLCreateMigrationsTable(t *testing.T) { + drv := testBigqueryDriver(t) + drv.migrationsTableName = "test_migrations_1" + + db, err := getDbConnection(drv.databaseURL.String()) + require.NoError(t, err) + defer dbutil.MustClose(db) + + // migrations table should not exist + count := 0 + err = db.QueryRow("select count(*) from test_migrations_1").Scan(&count) + require.Error(t, err) + + // create table + err = drv.CreateMigrationsTable(db) + require.NoError(t, err) + + // migrations table should exist + err = db.QueryRow("select count(*) from test_migrations_1").Scan(&count) + require.NoError(t, err) + + // create table should be idempotent + err = drv.CreateMigrationsTable(db) + require.NoError(t, err) +} + +func TestMySQLSelectMigrations(t *testing.T) { + drv := testBigqueryDriver(t) + drv.migrationsTableName = "test_migrations_2" + + db, err := getDbConnection(drv.databaseURL.String()) + require.NoError(t, err) + defer dbutil.MustClose(db) + + err = drv.CreateMigrationsTable(db) + require.NoError(t, err) + + _, err = db.Exec(`insert into test_migrations_2 (version) + values ('abc2'), ('abc1'), ('abc3')`) + require.NoError(t, err) + + migrations, err := drv.SelectMigrations(db, -1) + require.NoError(t, err) + require.Equal(t, true, migrations["abc1"]) + require.Equal(t, true, migrations["abc2"]) + require.Equal(t, true, migrations["abc2"]) + + // test limit param + migrations, err = drv.SelectMigrations(db, 1) + require.NoError(t, err) + require.Equal(t, true, migrations["abc3"]) + require.Equal(t, false, migrations["abc1"]) + require.Equal(t, false, migrations["abc2"]) +} + +func TestMySQLInsertMigration(t *testing.T) { + drv := testBigqueryDriver(t) + drv.migrationsTableName = "test_migrations_3" + + db, err := getDbConnection(drv.databaseURL.String()) + require.NoError(t, err) + defer dbutil.MustClose(db) + + err = drv.CreateMigrationsTable(db) + require.NoError(t, err) + + count := 0 + err = db.QueryRow("select count(*) from test_migrations_3").Scan(&count) + require.NoError(t, err) + require.Equal(t, 0, count) + + // insert migration + err = drv.InsertMigration(db, "abc1") + require.NoError(t, err) + + err = db.QueryRow("select count(*) from test_migrations_3 where version = 'abc1'").Scan(&count) + require.NoError(t, err) + require.Equal(t, 1, count) +} + +func TestMySQLDeleteMigration(t *testing.T) { + drv := testBigqueryDriver(t) + drv.migrationsTableName = "test_migrations_4" + + db, err := getDbConnection(drv.databaseURL.String()) + require.NoError(t, err) + defer dbutil.MustClose(db) + + err = drv.CreateMigrationsTable(db) + require.NoError(t, err) + + _, err = db.Exec(`insert into test_migrations_4 (version) values ('abc1'), ('abc2')`) + require.NoError(t, err) + + err = drv.DeleteMigration(db, "abc2") + require.NoError(t, err) + + count := 0 + err = db.QueryRow("select count(*) from test_migrations_4").Scan(&count) + require.NoError(t, err) + require.Equal(t, 1, count) +} + +func TestMySQLDatabaseExists(t *testing.T) { + drv := testBigqueryDriver(t) + + // drop any existing database + err := drv.DropDatabase() + require.NoError(t, err) + + // DatabaseExists should return false + exists, err := drv.DatabaseExists() + require.NoError(t, err) + require.Equal(t, false, exists) + + // create database + err = drv.CreateDatabase() + require.NoError(t, err) + + // DatabaseExists should return true + exists, err = drv.DatabaseExists() + require.NoError(t, err) + require.Equal(t, true, exists) +} diff --git a/pkg/driver/bigquery/test_cred.json b/pkg/driver/bigquery/test_cred.json new file mode 100644 index 00000000..0d8a7def --- /dev/null +++ b/pkg/driver/bigquery/test_cred.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "test-project", + "private_key_id": "someprivatekey", + "private_key": "\\n-----END PRIVATE KEY-----\n", + "client_email": "test@project.iam.gserviceaccount.com", + "client_id": "100000000000000000001", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dataset%40project.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} From c74a616ff12b79e256c7d2249d00c5440ba0d3a6 Mon Sep 17 00:00:00 2001 From: mii9000 Date: Sun, 3 Mar 2024 11:22:00 +0600 Subject: [PATCH 02/17] fixes lint issues --- main.go | 10 +++++----- pkg/driver/bigquery/bigquery.go | 27 ++++++++++++++++++-------- pkg/driver/bigquery/bigquery_client.go | 4 ++-- pkg/driver/bigquery/bigquery_test.go | 9 +++++---- 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/main.go b/main.go index aa5f2f31..4b4c9308 100644 --- a/main.go +++ b/main.go @@ -133,14 +133,14 @@ func NewApp() *cli.App { { Name: "create", Usage: "Create database", - Action: action(func(db *dbmate.DB, c *cli.Context) error { + Action: action(func(db *dbmate.DB, _ *cli.Context) error { return db.Create() }), }, { Name: "drop", Usage: "Drop database (if it exists)", - Action: action(func(db *dbmate.DB, c *cli.Context) error { + Action: action(func(db *dbmate.DB, _ *cli.Context) error { return db.Drop() }), }, @@ -219,21 +219,21 @@ func NewApp() *cli.App { { Name: "dump", Usage: "Write the database schema to disk", - Action: action(func(db *dbmate.DB, c *cli.Context) error { + Action: action(func(db *dbmate.DB, _ *cli.Context) error { return db.DumpSchema() }), }, { Name: "load", Usage: "Load schema file to the database", - Action: action(func(db *dbmate.DB, c *cli.Context) error { + Action: action(func(db *dbmate.DB, _ *cli.Context) error { return db.LoadSchema() }), }, { Name: "wait", Usage: "Wait for the database to become available", - Action: action(func(db *dbmate.DB, c *cli.Context) error { + Action: action(func(db *dbmate.DB, _ *cli.Context) error { return db.Wait() }), }, diff --git a/pkg/driver/bigquery/bigquery.go b/pkg/driver/bigquery/bigquery.go index 42832079..4e5bcadb 100644 --- a/pkg/driver/bigquery/bigquery.go +++ b/pkg/driver/bigquery/bigquery.go @@ -9,11 +9,12 @@ import ( "strings" "cloud.google.com/go/bigquery" - "github.com/amacneil/dbmate/v2/pkg/dbmate" - "github.com/amacneil/dbmate/v2/pkg/dbutil" "google.golang.org/api/googleapi" "google.golang.org/api/iterator" - _ "gorm.io/driver/bigquery" + _ "gorm.io/driver/bigquery" // database/sql driver + + "github.com/amacneil/dbmate/v2/pkg/dbmate" + "github.com/amacneil/dbmate/v2/pkg/dbutil" ) func init() { @@ -54,7 +55,7 @@ func (drv *Driver) withBigQueryClient(operation func(context.Context, *bigquery. } // Helper function to check whether a table exists or not in a dataset -func tableExists(client *bigquery.Client, ctx context.Context, datasetID, tableName string) (bool, error) { +func tableExists(ctx context.Context, client *bigquery.Client, datasetID, tableName string) (bool, error) { table := client.Dataset(datasetID).Table(tableName) _, err := table.Metadata(ctx) if err == nil { @@ -98,9 +99,9 @@ func (drv *Driver) CreateDatabase() error { return nil } -func (drv *Driver) CreateMigrationsTable(db *sql.DB) error { +func (drv *Driver) CreateMigrationsTable(*sql.DB) error { tableExists := func(ctx context.Context, client *bigquery.Client) (bool, error) { - return tableExists(client, ctx, drv.datasetID, drv.migrationsTableName) + return tableExists(ctx, client, drv.datasetID, drv.migrationsTableName) } createTable := func(ctx context.Context, client *bigquery.Client) (bool, error) { @@ -206,6 +207,11 @@ func generateDDL(db *sql.DB, projectID, datasetID, objectName, objectType string } defer dbutil.MustClose(rows) + // Check for any error that occurred during the query execution + if err := rows.Err(); err != nil { + return "", err + } + // Generate DDL for tables for rows.Next() { var columnName, dataType, isNullable string @@ -272,6 +278,11 @@ func generateDDL(db *sql.DB, projectID, datasetID, objectName, objectType string } defer dbutil.MustClose(paramRows) + // Check for any error that occurred during the query execution + if err := paramRows.Err(); err != nil { + return "", err + } + // Construct function parameters list var paramList []string var returnType string @@ -363,9 +374,9 @@ func (drv *Driver) DumpSchema(db *sql.DB) ([]byte, error) { return script, nil } -func (drv *Driver) MigrationsTableExists(db *sql.DB) (bool, error) { +func (drv *Driver) MigrationsTableExists(*sql.DB) (bool, error) { tableExists := func(ctx context.Context, client *bigquery.Client) (bool, error) { - return tableExists(client, ctx, drv.datasetID, drv.migrationsTableName) + return tableExists(ctx, client, drv.datasetID, drv.migrationsTableName) } exists, err := drv.withBigQueryClient(tableExists) diff --git a/pkg/driver/bigquery/bigquery_client.go b/pkg/driver/bigquery/bigquery_client.go index 0d8d3674..3be31a2a 100644 --- a/pkg/driver/bigquery/bigquery_client.go +++ b/pkg/driver/bigquery/bigquery_client.go @@ -21,7 +21,7 @@ type bigQueryConfig struct { } func GetClient(ctx context.Context, uri string) (*bigquery.Client, error) { - config, err := configFromUri(uri) + config, err := configFromURI(uri) if err != nil { return nil, err } @@ -45,7 +45,7 @@ func GetClient(ctx context.Context, uri string) (*bigquery.Client, error) { return client, nil } -func configFromUri(uri string) (*bigQueryConfig, error) { +func configFromURI(uri string) (*bigQueryConfig, error) { u, err := url.Parse(uri) if err != nil { return nil, invalidConnectionStringError(uri) diff --git a/pkg/driver/bigquery/bigquery_test.go b/pkg/driver/bigquery/bigquery_test.go index 3ff37263..008e5af1 100644 --- a/pkg/driver/bigquery/bigquery_test.go +++ b/pkg/driver/bigquery/bigquery_test.go @@ -6,12 +6,13 @@ import ( "os" "testing" + "github.com/stretchr/testify/require" + "github.com/amacneil/dbmate/v2/pkg/dbmate" "github.com/amacneil/dbmate/v2/pkg/dbutil" - "github.com/stretchr/testify/require" ) -func getUrl() string { +func getURL() string { return os.Getenv("BIGQUERY_TEST_URL") } @@ -20,7 +21,7 @@ func getDbConnection(url string) (*sql.DB, error) { } func testBigqueryDriver(t *testing.T) *Driver { - u := dbutil.MustParseURL(getUrl()) + u := dbutil.MustParseURL(getURL()) drv, err := dbmate.New(u).Driver() require.NoError(t, err) @@ -46,7 +47,7 @@ func prepTestDB(t *testing.T) *sql.DB { } func TestGetDriver(t *testing.T) { - db := dbmate.New(dbutil.MustParseURL(getUrl())) + db := dbmate.New(dbutil.MustParseURL(getURL())) drvInterface, err := db.Driver() require.NoError(t, err) From 788a50b02a80f738f6c5e6e0022c9a4429b15106 Mon Sep 17 00:00:00 2001 From: mii9000 Date: Sun, 3 Mar 2024 13:06:13 +0600 Subject: [PATCH 03/17] fixes test case names --- pkg/driver/bigquery/bigquery_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/driver/bigquery/bigquery_test.go b/pkg/driver/bigquery/bigquery_test.go index 008e5af1..930db51d 100644 --- a/pkg/driver/bigquery/bigquery_test.go +++ b/pkg/driver/bigquery/bigquery_test.go @@ -74,7 +74,7 @@ func TestConnectionString(t *testing.T) { } } -func TestMySQLCreateDropDatabase(t *testing.T) { +func TestBigQueryCreateDropDatabase(t *testing.T) { drv := testBigqueryDriver(t) // drop any existing database @@ -130,7 +130,7 @@ func TestBigqueryCreateAndInsertMigration(t *testing.T) { require.NoError(t, err) } -func TestMySQLCreateMigrationsTable(t *testing.T) { +func TestBigQueryCreateMigrationsTable(t *testing.T) { drv := testBigqueryDriver(t) drv.migrationsTableName = "test_migrations_1" @@ -156,7 +156,7 @@ func TestMySQLCreateMigrationsTable(t *testing.T) { require.NoError(t, err) } -func TestMySQLSelectMigrations(t *testing.T) { +func TestBigQuerySelectMigrations(t *testing.T) { drv := testBigqueryDriver(t) drv.migrationsTableName = "test_migrations_2" @@ -185,7 +185,7 @@ func TestMySQLSelectMigrations(t *testing.T) { require.Equal(t, false, migrations["abc2"]) } -func TestMySQLInsertMigration(t *testing.T) { +func TestBigQueryInsertMigration(t *testing.T) { drv := testBigqueryDriver(t) drv.migrationsTableName = "test_migrations_3" @@ -210,7 +210,7 @@ func TestMySQLInsertMigration(t *testing.T) { require.Equal(t, 1, count) } -func TestMySQLDeleteMigration(t *testing.T) { +func TestBigQueryDeleteMigration(t *testing.T) { drv := testBigqueryDriver(t) drv.migrationsTableName = "test_migrations_4" @@ -233,7 +233,7 @@ func TestMySQLDeleteMigration(t *testing.T) { require.Equal(t, 1, count) } -func TestMySQLDatabaseExists(t *testing.T) { +func TestBigQueryDatabaseExists(t *testing.T) { drv := testBigqueryDriver(t) // drop any existing database From 88977c708d243ae5be21df5e5aa3bf102e6ce3e7 Mon Sep 17 00:00:00 2001 From: mii9000 Date: Sun, 3 Mar 2024 15:26:56 +0600 Subject: [PATCH 04/17] fixes bigquery emulator connection from inside docker network --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 5e19483c..c68e5c46 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: MYSQL_TEST_URL: mysql://root:root@mysql/dbmate_test POSTGRES_TEST_URL: postgres://postgres:postgres@postgres/dbmate_test?sslmode=disable SQLITE_TEST_URL: sqlite3:/tmp/dbmate_test.sqlite3 - BIGQUERY_TEST_URL: bigquery://test/dbmate_test?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true + BIGQUERY_TEST_URL: bigquery://test/dbmate_test?endpoint=http%3A%2F%2Fbigquery%3A9050&disable_auth=true dbmate: build: @@ -69,6 +69,6 @@ services: bigquery: image: ghcr.io/goccy/bigquery-emulator:0.4.4 ports: - - "9050:9050" + - "0.0.0.0:9050:9050" command: | --project=test --dataset=dbmate_test From 7593045eca707ee99ad14115b51b6cb8796242ef Mon Sep 17 00:00:00 2001 From: mii9000 Date: Mon, 4 Mar 2024 17:48:11 +0600 Subject: [PATCH 05/17] removes bigquery client, keeps a single client and removes endpoint from uri --- docker-compose.yml | 4 +- pkg/driver/bigquery/bigquery.go | 438 ++++++++++++-------- pkg/driver/bigquery/bigquery_client.go | 84 ---- pkg/driver/bigquery/bigquery_client_test.go | 88 ---- pkg/driver/bigquery/bigquery_test.go | 16 +- 5 files changed, 266 insertions(+), 364 deletions(-) delete mode 100644 pkg/driver/bigquery/bigquery_client.go delete mode 100644 pkg/driver/bigquery/bigquery_client_test.go diff --git a/docker-compose.yml b/docker-compose.yml index c68e5c46..e2487bd4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: MYSQL_TEST_URL: mysql://root:root@mysql/dbmate_test POSTGRES_TEST_URL: postgres://postgres:postgres@postgres/dbmate_test?sslmode=disable SQLITE_TEST_URL: sqlite3:/tmp/dbmate_test.sqlite3 - BIGQUERY_TEST_URL: bigquery://test/dbmate_test?endpoint=http%3A%2F%2Fbigquery%3A9050&disable_auth=true + BIGQUERY_TEST_URL: bigquery://bigquery:9050/test/dbmate_test?disable_auth=true dbmate: build: @@ -68,7 +68,5 @@ services: bigquery: image: ghcr.io/goccy/bigquery-emulator:0.4.4 - ports: - - "0.0.0.0:9050:9050" command: | --project=test --dataset=dbmate_test diff --git a/pkg/driver/bigquery/bigquery.go b/pkg/driver/bigquery/bigquery.go index 4e5bcadb..41728d6c 100644 --- a/pkg/driver/bigquery/bigquery.go +++ b/pkg/driver/bigquery/bigquery.go @@ -6,75 +6,63 @@ import ( "fmt" "io" "net/url" + "strconv" "strings" "cloud.google.com/go/bigquery" "google.golang.org/api/googleapi" "google.golang.org/api/iterator" + "google.golang.org/api/option" _ "gorm.io/driver/bigquery" // database/sql driver "github.com/amacneil/dbmate/v2/pkg/dbmate" "github.com/amacneil/dbmate/v2/pkg/dbutil" ) +type Driver struct { + migrationsTableName string + datasetID string + projectID string + log io.Writer + databaseURL string + client *bigquery.Client + context *context.Context + config *bigQueryConfig + sqlConnectionURL string +} + +type bigQueryConfig struct { + projectID string + location string + dataSet string + scopes []string + endpoint string + disableAuth bool +} + func init() { dbmate.RegisterDriver(NewDriver, "bigquery") } func NewDriver(config dbmate.DriverConfig) dbmate.Driver { - u := config.DatabaseURL + c, _ := configFromURI(config.DatabaseURL.String()) + params := fmt.Sprintf("disable_auth=%s", strconv.FormatBool(c.disableAuth)) + if c.endpoint != "" { + params += fmt.Sprintf("&endpoint=%s", c.endpoint) + } + u := fmt.Sprintf("bigquery://%s/%s?%s", c.projectID, c.dataSet, params) return &Driver{ migrationsTableName: config.MigrationsTableName, - datasetID: strings.TrimPrefix(u.Path, "/"), - projectID: u.Host, + datasetID: c.dataSet, + projectID: c.projectID, log: config.Log, + config: c, databaseURL: u, + sqlConnectionURL: config.DatabaseURL.String(), } } -// Helper function that accepts an operation function and executes it with a BigQuery client. -func (drv *Driver) withBigQueryClient(operation func(context.Context, *bigquery.Client) (bool, error)) (bool, error) { - ctx := context.Background() - - // Create a BigQuery client. - client, err := GetClient(ctx, drv.databaseURL.String()) - if err != nil { - return false, err - } - defer client.Close() - - // Execute the operation function with the client. - result, err := operation(ctx, client) - - if err != nil { - return result, err - } - - return result, nil -} - -// Helper function to check whether a table exists or not in a dataset -func tableExists(ctx context.Context, client *bigquery.Client, datasetID, tableName string) (bool, error) { - table := client.Dataset(datasetID).Table(tableName) - _, err := table.Metadata(ctx) - if err == nil { - return true, nil - } - if gError, ok := err.(*googleapi.Error); ok && gError.Code == 404 { - return false, nil - } - return false, err -} - -type Driver struct { - migrationsTableName string - datasetID string - projectID string - log io.Writer - databaseURL *url.URL -} - func (drv *Driver) CreateDatabase() error { exists, err := drv.DatabaseExists() if err != nil { @@ -90,7 +78,7 @@ func (drv *Driver) CreateDatabase() error { } if !exists { - _, err := drv.withBigQueryClient(createDataset) + _, err := createDataset(*drv.context, drv.client) if err != nil { return err } @@ -118,13 +106,13 @@ func (drv *Driver) CreateMigrationsTable(*sql.DB) error { return true, nil } - exists, err := drv.withBigQueryClient(tableExists) + exists, err := tableExists(*drv.context, drv.client) if err != nil { return err } if !exists { - _, err := drv.withBigQueryClient(createTable) + _, err := createTable(*drv.context, drv.client) if err != nil { return err } @@ -151,7 +139,7 @@ func (drv *Driver) DatabaseExists() (bool, error) { return false, nil } - exists, err := drv.withBigQueryClient(datasetExists) + exists, err := datasetExists(*drv.context, drv.client) if err != nil { return false, err } @@ -174,7 +162,7 @@ func (drv *Driver) DropDatabase() error { } if exists { - _, err = drv.withBigQueryClient(datasetDrop) + _, err = datasetDrop(*drv.context, drv.client) if err != nil { return err } @@ -183,133 +171,6 @@ func (drv *Driver) DropDatabase() error { return nil } -func generateDDL(db *sql.DB, projectID, datasetID, objectName, objectType string) (string, error) { - var ddl string - - // Query to retrieve object information - switch objectType { - case "BASE TABLE": - // Query to retrieve column information for tables - query := fmt.Sprintf(` - SELECT - column_name, - data_type, - is_nullable - FROM - `+"`%s.%s.INFORMATION_SCHEMA.COLUMNS`"+` - WHERE - table_name = '%s'`, projectID, datasetID, objectName) - - // Execute the query - rows, err := db.Query(query) - if err != nil { - return "", err - } - defer dbutil.MustClose(rows) - - // Check for any error that occurred during the query execution - if err := rows.Err(); err != nil { - return "", err - } - - // Generate DDL for tables - for rows.Next() { - var columnName, dataType, isNullable string - if err := rows.Scan(&columnName, &dataType, &isNullable); err != nil { - return "", err - } - if isNullable == "YES" { - ddl += fmt.Sprintf("\t%s %s,\n", columnName, dataType) - } else { - ddl += fmt.Sprintf("\t%s %s %s,\n", columnName, dataType, "NOT NULL") - } - } - if ddl != "" { - ddl = fmt.Sprintf("CREATE TABLE `%s.%s.%s` (\n%s);", projectID, datasetID, objectName, ddl) - } - case "VIEW": - // Query to retrieve view definition - query := fmt.Sprintf(` - SELECT - view_definition - FROM - `+"`%s.%s.INFORMATION_SCHEMA.VIEWS`"+` - WHERE - table_name = '%s'`, projectID, datasetID, objectName) - - // Execute the query - row := db.QueryRow(query) - if err := row.Scan(&ddl); err != nil { - return "", err - } - ddl = fmt.Sprintf("CREATE VIEW `%s.%s.%s` AS\n%s;", projectID, datasetID, objectName, ddl) - ddl = strings.ReplaceAll(ddl, "\n", "\n\t") - case "FUNCTION": - // Query to retrieve function definition - definitionQuery := fmt.Sprintf(` - SELECT - routine_definition - FROM - `+"`%s.%s.INFORMATION_SCHEMA.ROUTINES`"+` - WHERE - routine_name = '%s'`, projectID, datasetID, objectName) - - // Execute the query to fetch function definition - definitionRow := db.QueryRow(definitionQuery) - if err := definitionRow.Scan(&ddl); err != nil { - return "", err - } - - // Query to retrieve function parameters - paramQuery := fmt.Sprintf(` - SELECT - parameter_name, - data_type, - ordinal_position - FROM - `+"`%s.%s.INFORMATION_SCHEMA.PARAMETERS`"+` - WHERE - specific_name = '%s'`, projectID, datasetID, objectName) - - // Execute the query to fetch function parameters - paramRows, err := db.Query(paramQuery) - if err != nil { - return "", err - } - defer dbutil.MustClose(paramRows) - - // Check for any error that occurred during the query execution - if err := paramRows.Err(); err != nil { - return "", err - } - - // Construct function parameters list - var paramList []string - var returnType string - for paramRows.Next() { - var paramName sql.NullString - var dataType string - var ordinalPosition int - if err := paramRows.Scan(¶mName, &dataType, &ordinalPosition); err != nil { - return "", err - } - if ordinalPosition == 0 { - returnType = dataType - } else { - paramList = append(paramList, fmt.Sprintf("%s %s", paramName.String, dataType)) - } - } - params := strings.Join(paramList, ", ") - - // Construct the function DDL with parameters - ddl = fmt.Sprintf("CREATE FUNCTION `%s.%s.%s` (%s) RETURNS %s AS (\n\t%s\n);", projectID, datasetID, objectName, params, returnType, ddl) - default: - return "", fmt.Errorf("unsupported object type: %s", objectType) - } - - return ddl, nil -} - func (drv *Driver) DumpSchema(db *sql.DB) ([]byte, error) { var script []byte @@ -375,11 +236,7 @@ func (drv *Driver) DumpSchema(db *sql.DB) ([]byte, error) { } func (drv *Driver) MigrationsTableExists(*sql.DB) (bool, error) { - tableExists := func(ctx context.Context, client *bigquery.Client) (bool, error) { - return tableExists(ctx, client, drv.datasetID, drv.migrationsTableName) - } - - exists, err := drv.withBigQueryClient(tableExists) + exists, err := tableExists(*drv.context, drv.client, drv.datasetID, drv.migrationsTableName) if err != nil { return exists, err } @@ -403,7 +260,20 @@ func (drv *Driver) InsertMigration(db dbutil.Transaction, version string) error } func (drv *Driver) Open() (*sql.DB, error) { - con, err := sql.Open("bigquery", drv.databaseURL.String()) + con, err := sql.Open("bigquery", drv.databaseURL) + if err != nil { + return nil, err + } + + ctx := context.Background() + client, err := getClient(ctx, drv.config) + if err != nil { + return nil, err + } + + drv.client = client + drv.context = &ctx + return con, err } @@ -451,3 +321,205 @@ func (drv *Driver) SelectMigrations(db *sql.DB, limit int) (map[string]bool, err return migrations, nil } + +func configFromURI(uri string) (*bigQueryConfig, error) { + invalidError := func(uri string) error { + return fmt.Errorf("invalid connection string: %s", uri) + } + + u, err := url.Parse(uri) + if err != nil { + return nil, invalidError(uri) + } + + if u.Scheme != "bigquery" { + return nil, fmt.Errorf("invalid prefix, expected bigquery:// got: %s", uri) + } + + if u.Path == "" { + return nil, invalidError(uri) + } + + fields := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") + if len(fields) > 3 { + return nil, invalidError(uri) + } + + config := &bigQueryConfig{ + projectID: u.Hostname(), + dataSet: fields[len(fields)-1], + disableAuth: u.Query().Get("disable_auth") == "true", + } + + if u.Port() != "" { + config.endpoint = fmt.Sprintf("http://%s:%s", u.Hostname(), u.Port()) + config.projectID = fields[0] + } + + if len(fields) == 2 { + config.location = fields[0] + } + + if len(fields) == 3 { + config.location = fields[1] + } + + return config, nil +} + +func getClient(ctx context.Context, config *bigQueryConfig) (*bigquery.Client, error) { + opts := []option.ClientOption{option.WithScopes(config.scopes...)} + if config.endpoint != "" { + opts = append(opts, option.WithEndpoint(config.endpoint)) + } + if config.disableAuth { + opts = append(opts, option.WithoutAuthentication()) + } + + client, err := bigquery.NewClient(ctx, config.projectID, opts...) + if err != nil { + return nil, err + } + + return client, nil +} + +func generateDDL(db *sql.DB, projectID, datasetID, objectName, objectType string) (string, error) { + var ddl string + + // Query to retrieve object information + switch objectType { + case "BASE TABLE": + // Query to retrieve column information for tables + query := fmt.Sprintf(` + SELECT + column_name, + data_type, + is_nullable + FROM + `+"`%s.%s.INFORMATION_SCHEMA.COLUMNS`"+` + WHERE + table_name = '%s'`, projectID, datasetID, objectName) + + // Execute the query + rows, err := db.Query(query) + if err != nil { + return "", err + } + defer dbutil.MustClose(rows) + + // Check for any error that occurred during the query execution + if err := rows.Err(); err != nil { + return "", err + } + + // Generate DDL for tables + for rows.Next() { + var columnName, dataType, isNullable string + if err := rows.Scan(&columnName, &dataType, &isNullable); err != nil { + return "", err + } + if isNullable == "YES" { + ddl += fmt.Sprintf("\t%s %s,\n", columnName, dataType) + } else { + ddl += fmt.Sprintf("\t%s %s %s,\n", columnName, dataType, "NOT NULL") + } + } + if ddl != "" { + ddl = fmt.Sprintf("CREATE TABLE `%s.%s.%s` (\n%s);", projectID, datasetID, objectName, ddl) + } + case "VIEW": + // Query to retrieve view definition + query := fmt.Sprintf(` + SELECT + view_definition + FROM + `+"`%s.%s.INFORMATION_SCHEMA.VIEWS`"+` + WHERE + table_name = '%s'`, projectID, datasetID, objectName) + + // Execute the query + row := db.QueryRow(query) + if err := row.Scan(&ddl); err != nil { + return "", err + } + ddl = fmt.Sprintf("CREATE VIEW `%s.%s.%s` AS\n%s;", projectID, datasetID, objectName, ddl) + ddl = strings.ReplaceAll(ddl, "\n", "\n\t") + case "FUNCTION": + // Query to retrieve function definition + definitionQuery := fmt.Sprintf(` + SELECT + routine_definition + FROM + `+"`%s.%s.INFORMATION_SCHEMA.ROUTINES`"+` + WHERE + routine_name = '%s'`, projectID, datasetID, objectName) + + // Execute the query to fetch function definition + definitionRow := db.QueryRow(definitionQuery) + if err := definitionRow.Scan(&ddl); err != nil { + return "", err + } + + // Query to retrieve function parameters + paramQuery := fmt.Sprintf(` + SELECT + parameter_name, + data_type, + ordinal_position + FROM + `+"`%s.%s.INFORMATION_SCHEMA.PARAMETERS`"+` + WHERE + specific_name = '%s'`, projectID, datasetID, objectName) + + // Execute the query to fetch function parameters + paramRows, err := db.Query(paramQuery) + if err != nil { + return "", err + } + defer dbutil.MustClose(paramRows) + + // Check for any error that occurred during the query execution + if err := paramRows.Err(); err != nil { + return "", err + } + + // Construct function parameters list + var paramList []string + var returnType string + for paramRows.Next() { + var paramName sql.NullString + var dataType string + var ordinalPosition int + if err := paramRows.Scan(¶mName, &dataType, &ordinalPosition); err != nil { + return "", err + } + if ordinalPosition == 0 { + returnType = dataType + } else { + paramList = append(paramList, fmt.Sprintf("%s %s", paramName.String, dataType)) + } + } + params := strings.Join(paramList, ", ") + + // Construct the function DDL with parameters + ddl = fmt.Sprintf("CREATE FUNCTION `%s.%s.%s` (%s) RETURNS %s AS (\n\t%s\n);", projectID, datasetID, objectName, params, returnType, ddl) + default: + return "", fmt.Errorf("unsupported object type: %s", objectType) + } + + return ddl, nil +} + +// Helper function to check whether a table exists or not in a dataset +func tableExists(ctx context.Context, client *bigquery.Client, datasetID, tableName string) (bool, error) { + table := client.Dataset(datasetID).Table(tableName) + _, err := table.Metadata(ctx) + if err == nil { + return true, nil + } + if gError, ok := err.(*googleapi.Error); ok && gError.Code == 404 { + return false, nil + } + return false, err +} diff --git a/pkg/driver/bigquery/bigquery_client.go b/pkg/driver/bigquery/bigquery_client.go deleted file mode 100644 index 3be31a2a..00000000 --- a/pkg/driver/bigquery/bigquery_client.go +++ /dev/null @@ -1,84 +0,0 @@ -package bigquery - -import ( - "context" - "fmt" - "net/url" - "strings" - - "cloud.google.com/go/bigquery" - "google.golang.org/api/option" -) - -type bigQueryConfig struct { - projectID string - location string - dataSet string - scopes []string - endpoint string - disableAuth bool - credentialFile string -} - -func GetClient(ctx context.Context, uri string) (*bigquery.Client, error) { - config, err := configFromURI(uri) - if err != nil { - return nil, err - } - - opts := []option.ClientOption{option.WithScopes(config.scopes...)} - if config.endpoint != "" { - opts = append(opts, option.WithEndpoint(config.endpoint)) - } - if config.disableAuth { - opts = append(opts, option.WithoutAuthentication()) - } - if config.credentialFile != "" { - opts = append(opts, option.WithCredentialsFile(config.credentialFile)) - } - - client, err := bigquery.NewClient(ctx, config.projectID, opts...) - if err != nil { - return nil, err - } - - return client, nil -} - -func configFromURI(uri string) (*bigQueryConfig, error) { - u, err := url.Parse(uri) - if err != nil { - return nil, invalidConnectionStringError(uri) - } - - if u.Scheme != "bigquery" { - return nil, fmt.Errorf("invalid prefix, expected bigquery:// got: %s", uri) - } - - if u.Path == "" { - return nil, invalidConnectionStringError(uri) - } - - fields := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") - if len(fields) > 2 { - return nil, invalidConnectionStringError(uri) - } - - config := &bigQueryConfig{ - projectID: u.Hostname(), - dataSet: fields[len(fields)-1], - endpoint: u.Query().Get("endpoint"), - disableAuth: u.Query().Get("disable_auth") == "true", - credentialFile: u.Query().Get("credential_file"), - } - - if len(fields) == 2 { - config.location = fields[0] - } - - return config, nil -} - -func invalidConnectionStringError(uri string) error { - return fmt.Errorf("invalid connection string: %s", uri) -} diff --git a/pkg/driver/bigquery/bigquery_client_test.go b/pkg/driver/bigquery/bigquery_client_test.go deleted file mode 100644 index 6b3d78fd..00000000 --- a/pkg/driver/bigquery/bigquery_client_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package bigquery - -import ( - "context" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" -) - -func getCredFile(t *testing.T) string { - // Get the current working directory - cwd, err := os.Getwd() - if err != nil { - t.Fatalf("failed to get current working directory: %v", err) - } - - // Construct the absolute path to the credential file - return filepath.Join(cwd, "test_cred.json") -} - -func assert(cases []string, t *testing.T, isValid bool) { - for _, c := range cases { - t.Run(c, func(t *testing.T) { - ctx := context.Background() - _, err := GetClient(ctx, c) - if isValid { - if err != nil { - t.Errorf("unexpected error: %v", err) - } - } else { - require.Error(t, err) - } - }) - } -} - -func TestValidURIsWithoutCred(t *testing.T) { - cases := [4]string{ - "bigquery://projectid/dataset?disable_auth=true", - "bigquery://projectid/location/dataset?disable_auth=true", - "bigquery://projectid/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true", - "bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true", - } - - assert(cases[:], t, true) -} - -func TestValidURIsWithCred(t *testing.T) { - credentialFile := getCredFile(t) - - cases := [4]string{ - "bigquery://projectid/dataset?credential_file=" + credentialFile, - "bigquery://projectid/location/dataset?credential_file=" + credentialFile, - "bigquery://projectid/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&credential_file=" + credentialFile, - "bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&credential_file=" + credentialFile, - } - - assert(cases[:], t, true) -} - -func TestValidURIsWithEnvCred(t *testing.T) { - cases := [4]string{ - "bigquery://projectid/dataset", - "bigquery://projectid/location/dataset", - "bigquery://projectid/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050", - "bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050", - } - - env := "GOOGLE_APPLICATION_CREDENTIALS" - os.Setenv(env, getCredFile(t)) - defer os.Unsetenv(env) - - assert(cases[:], t, true) -} - -func TestInvalidURIs(t *testing.T) { - cases := [5]string{ - "bigquery://projectiddataset", - "bigquery://projectid/location/dataset/unknown", - "bigquery://projectid/dataset?endpoint=https://localhost:3000", - "bigquery://projectid/location/dataset?", - "bigquery://projectid/dataset?credential_file=does/not/exist", - } - - assert(cases[:], t, false) -} diff --git a/pkg/driver/bigquery/bigquery_test.go b/pkg/driver/bigquery/bigquery_test.go index 930db51d..08c8d115 100644 --- a/pkg/driver/bigquery/bigquery_test.go +++ b/pkg/driver/bigquery/bigquery_test.go @@ -25,6 +25,9 @@ func testBigqueryDriver(t *testing.T) *Driver { drv, err := dbmate.New(u).Driver() require.NoError(t, err) + _, err = drv.Open() + require.NoError(t, err) + return drv.(*Driver) } @@ -54,16 +57,17 @@ func TestGetDriver(t *testing.T) { // driver should have URL and default migrations table set drv, ok := drvInterface.(*Driver) require.True(t, ok) - require.Equal(t, db.DatabaseURL.String(), drv.databaseURL.String()) + require.Equal(t, db.DatabaseURL.String(), drv.sqlConnectionURL) require.Equal(t, "schema_migrations", drv.migrationsTableName) } func TestConnectionString(t *testing.T) { - cases := [4]string{ + cases := [5]string{ "bigquery://projectid/dataset", "bigquery://projectid/location/dataset", "bigquery://projectid/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050", "bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true", + "bigquery://bigquery:9050/test/dbmate_test?disable_auth=true", } for _, c := range cases { @@ -134,7 +138,7 @@ func TestBigQueryCreateMigrationsTable(t *testing.T) { drv := testBigqueryDriver(t) drv.migrationsTableName = "test_migrations_1" - db, err := getDbConnection(drv.databaseURL.String()) + db, err := getDbConnection(drv.databaseURL) require.NoError(t, err) defer dbutil.MustClose(db) @@ -160,7 +164,7 @@ func TestBigQuerySelectMigrations(t *testing.T) { drv := testBigqueryDriver(t) drv.migrationsTableName = "test_migrations_2" - db, err := getDbConnection(drv.databaseURL.String()) + db, err := getDbConnection(drv.databaseURL) require.NoError(t, err) defer dbutil.MustClose(db) @@ -189,7 +193,7 @@ func TestBigQueryInsertMigration(t *testing.T) { drv := testBigqueryDriver(t) drv.migrationsTableName = "test_migrations_3" - db, err := getDbConnection(drv.databaseURL.String()) + db, err := getDbConnection(drv.databaseURL) require.NoError(t, err) defer dbutil.MustClose(db) @@ -214,7 +218,7 @@ func TestBigQueryDeleteMigration(t *testing.T) { drv := testBigqueryDriver(t) drv.migrationsTableName = "test_migrations_4" - db, err := getDbConnection(drv.databaseURL.String()) + db, err := getDbConnection(drv.databaseURL) require.NoError(t, err) defer dbutil.MustClose(db) From baf2c8cdfda357749612f979192c84c59dc2a7be Mon Sep 17 00:00:00 2001 From: mii9000 Date: Sun, 10 Mar 2024 12:15:00 +0600 Subject: [PATCH 06/17] fixes to make bigquery more synonymous to other existing drivers --- pkg/driver/bigquery/bigquery.go | 189 +++++++++++++++------------ pkg/driver/bigquery/bigquery_test.go | 140 ++++++++++---------- pkg/driver/bigquery/test_cred.json | 13 -- 3 files changed, 170 insertions(+), 172 deletions(-) delete mode 100644 pkg/driver/bigquery/test_cred.json diff --git a/pkg/driver/bigquery/bigquery.go b/pkg/driver/bigquery/bigquery.go index 41728d6c..e2a893cd 100644 --- a/pkg/driver/bigquery/bigquery.go +++ b/pkg/driver/bigquery/bigquery.go @@ -24,11 +24,9 @@ type Driver struct { datasetID string projectID string log io.Writer - databaseURL string + databaseURL *url.URL client *bigquery.Client context *context.Context - config *bigQueryConfig - sqlConnectionURL string } type bigQueryConfig struct { @@ -45,40 +43,31 @@ func init() { } func NewDriver(config dbmate.DriverConfig) dbmate.Driver { - c, _ := configFromURI(config.DatabaseURL.String()) - params := fmt.Sprintf("disable_auth=%s", strconv.FormatBool(c.disableAuth)) - if c.endpoint != "" { - params += fmt.Sprintf("&endpoint=%s", c.endpoint) - } - u := fmt.Sprintf("bigquery://%s/%s?%s", c.projectID, c.dataSet, params) + c, _ := configFromURI(config.DatabaseURL) return &Driver{ migrationsTableName: config.MigrationsTableName, datasetID: c.dataSet, projectID: c.projectID, log: config.Log, - config: c, - databaseURL: u, - sqlConnectionURL: config.DatabaseURL.String(), + databaseURL: config.DatabaseURL, } } func (drv *Driver) CreateDatabase() error { - exists, err := drv.DatabaseExists() + db, err := drv.Open() if err != nil { return err } + defer dbutil.MustClose(db) - createDataset := func(ctx context.Context, client *bigquery.Client) (bool, error) { - err := client.Dataset(drv.datasetID).Create(ctx, &bigquery.DatasetMetadata{}) - if err != nil { - return false, err - } - return true, nil + exists, err := drv.DatabaseExists() + if err != nil { + return err } if !exists { - _, err := createDataset(*drv.context, drv.client) + err := drv.client.Dataset(drv.datasetID).Create(*drv.context, &bigquery.DatasetMetadata{}) if err != nil { return err } @@ -88,73 +77,66 @@ func (drv *Driver) CreateDatabase() error { } func (drv *Driver) CreateMigrationsTable(*sql.DB) error { - tableExists := func(ctx context.Context, client *bigquery.Client) (bool, error) { - return tableExists(ctx, client, drv.datasetID, drv.migrationsTableName) - } - - createTable := func(ctx context.Context, client *bigquery.Client) (bool, error) { - table := client.Dataset(drv.datasetID).Table(drv.migrationsTableName) - err := table.Create(ctx, &bigquery.TableMetadata{ - Schema: bigquery.Schema{ - {Name: "version", Type: bigquery.StringFieldType}, - }, - }) - if err != nil { - return false, err - } - - return true, nil + err := addBigQueryClientToDriver(drv) + if err != nil { + return err } - exists, err := tableExists(*drv.context, drv.client) + exists, err := tableExists(*drv.context, drv.client, drv.datasetID, drv.migrationsTableName) if err != nil { return err } if !exists { - _, err := createTable(*drv.context, drv.client) + table := drv.client.Dataset(drv.datasetID).Table(drv.migrationsTableName) + err := table.Create(*drv.context, &bigquery.TableMetadata{ + Schema: bigquery.Schema{ + { + Name: "version", + Type: bigquery.StringFieldType, + }, + }, + }) if err != nil { return err } + + return nil } return nil } func (drv *Driver) DatabaseExists() (bool, error) { - datasetExists := func(ctx context.Context, client *bigquery.Client) (bool, error) { - it := client.Datasets(ctx) - for { - dataset, err := it.Next() - if err == iterator.Done { - break - } - if err != nil { - return false, err - } - if dataset.DatasetID == drv.datasetID { - return true, nil - } - } - return false, nil - } - - exists, err := datasetExists(*drv.context, drv.client) + db, err := drv.Open() if err != nil { return false, err } + defer dbutil.MustClose(db) - return exists, nil -} - -func (drv *Driver) DropDatabase() error { - datasetDrop := func(ctx context.Context, client *bigquery.Client) (bool, error) { - err := client.Dataset(drv.datasetID).DeleteWithContents(ctx) + it := drv.client.Datasets(*drv.context) + for { + dataset, err := it.Next() + if err == iterator.Done { + break + } if err != nil { return false, err } - return true, nil + if dataset.DatasetID == drv.datasetID { + return true, nil + } + } + + return false, nil +} + +func (drv *Driver) DropDatabase() error { + db, err := drv.Open() + if err != nil { + return err } + defer dbutil.MustClose(db) exists, err := drv.DatabaseExists() if err != nil { @@ -162,7 +144,7 @@ func (drv *Driver) DropDatabase() error { } if exists { - _, err = datasetDrop(*drv.context, drv.client) + err := drv.client.Dataset(drv.datasetID).DeleteWithContents(*drv.context) if err != nil { return err } @@ -236,6 +218,11 @@ func (drv *Driver) DumpSchema(db *sql.DB) ([]byte, error) { } func (drv *Driver) MigrationsTableExists(*sql.DB) (bool, error) { + err := addBigQueryClientToDriver(drv) + if err != nil { + return false, err + } + exists, err := tableExists(*drv.context, drv.client, drv.datasetID, drv.migrationsTableName) if err != nil { return exists, err @@ -260,20 +247,17 @@ func (drv *Driver) InsertMigration(db dbutil.Transaction, version string) error } func (drv *Driver) Open() (*sql.DB, error) { - con, err := sql.Open("bigquery", drv.databaseURL) + connString := connectionString(drv.databaseURL) + con, err := sql.Open("bigquery", connString) if err != nil { return nil, err } - ctx := context.Background() - client, err := getClient(ctx, drv.config) + err = addBigQueryClientToDriver(drv) if err != nil { return nil, err } - drv.client = client - drv.context = &ctx - return con, err } @@ -322,14 +306,34 @@ func (drv *Driver) SelectMigrations(db *sql.DB, limit int) (map[string]bool, err return migrations, nil } -func configFromURI(uri string) (*bigQueryConfig, error) { - invalidError := func(uri string) error { - return fmt.Errorf("invalid connection string: %s", uri) +func addBigQueryClientToDriver(drv *Driver) error { + config, err := configFromURI(drv.databaseURL) + if err != nil { + return err } - u, err := url.Parse(uri) + if drv.client == nil || drv.context == nil { + ctx := context.Background() + client, err := getClient(ctx, config) + if err != nil { + return err + } + + drv.client = client + drv.context = &ctx + } + + return nil +} + +func configFromURI(dbURL *url.URL) (*bigQueryConfig, error) { + invalidErrorMessage := "invalid connection string: %s" + + uri := dbURL.String() + + u, err := dbURL.Parse(uri) if err != nil { - return nil, invalidError(uri) + return nil, fmt.Errorf(invalidErrorMessage, uri) } if u.Scheme != "bigquery" { @@ -337,12 +341,12 @@ func configFromURI(uri string) (*bigQueryConfig, error) { } if u.Path == "" { - return nil, invalidError(uri) + return nil, fmt.Errorf(invalidErrorMessage, uri) } fields := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") if len(fields) > 3 { - return nil, invalidError(uri) + return nil, fmt.Errorf(invalidErrorMessage, uri) } config := &bigQueryConfig{ @@ -354,14 +358,13 @@ func configFromURI(uri string) (*bigQueryConfig, error) { if u.Port() != "" { config.endpoint = fmt.Sprintf("http://%s:%s", u.Hostname(), u.Port()) config.projectID = fields[0] - } - - if len(fields) == 2 { - config.location = fields[0] - } - - if len(fields) == 3 { - config.location = fields[1] + if len(fields) == 3 { + config.location = fields[1] + } + } else { + if len(fields) == 2 { + config.location = fields[0] + } } return config, nil @@ -523,3 +526,19 @@ func tableExists(ctx context.Context, client *bigquery.Client, datasetID, tableN } return false, err } + +func connectionString(url *url.URL) string { + c, _ := configFromURI(url) + + params := fmt.Sprintf("disable_auth=%s", strconv.FormatBool(c.disableAuth)) + if c.endpoint != "" { + params += fmt.Sprintf("&endpoint=%s", c.endpoint) + } + + var locationParam string + if c.location != "" { + locationParam = "/" + c.location + } + + return fmt.Sprintf("bigquery://%s%s/%s?%s", c.projectID, locationParam, c.dataSet, params) +} diff --git a/pkg/driver/bigquery/bigquery_test.go b/pkg/driver/bigquery/bigquery_test.go index 08c8d115..417aadb8 100644 --- a/pkg/driver/bigquery/bigquery_test.go +++ b/pkg/driver/bigquery/bigquery_test.go @@ -16,23 +16,16 @@ func getURL() string { return os.Getenv("BIGQUERY_TEST_URL") } -func getDbConnection(url string) (*sql.DB, error) { - return sql.Open("bigquery", url) -} - -func testBigqueryDriver(t *testing.T) *Driver { +func testBigQueryDriver(t *testing.T) *Driver { u := dbutil.MustParseURL(getURL()) drv, err := dbmate.New(u).Driver() require.NoError(t, err) - _, err = drv.Open() - require.NoError(t, err) - return drv.(*Driver) } -func prepTestDB(t *testing.T) *sql.DB { - drv := testBigqueryDriver(t) +func prepTestBigQueryDB(t *testing.T) *sql.DB { + drv := testBigQueryDriver(t) // drop any existing database err := drv.DropDatabase() @@ -50,36 +43,42 @@ func prepTestDB(t *testing.T) *sql.DB { } func TestGetDriver(t *testing.T) { - db := dbmate.New(dbutil.MustParseURL(getURL())) + db := dbmate.New(dbutil.MustParseURL("bigquery://test/db_mate")) drvInterface, err := db.Driver() require.NoError(t, err) // driver should have URL and default migrations table set drv, ok := drvInterface.(*Driver) require.True(t, ok) - require.Equal(t, db.DatabaseURL.String(), drv.sqlConnectionURL) + require.Equal(t, db.DatabaseURL.String(), drv.databaseURL.String()) require.Equal(t, "schema_migrations", drv.migrationsTableName) } func TestConnectionString(t *testing.T) { - cases := [5]string{ - "bigquery://projectid/dataset", - "bigquery://projectid/location/dataset", - "bigquery://projectid/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050", - "bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true", - "bigquery://bigquery:9050/test/dbmate_test?disable_auth=true", + cases := []struct { + input string + expected string + }{ + {"bigquery://projectid/dataset", "bigquery://projectid/dataset?disable_auth=false"}, + {"bigquery://projectid/location/dataset", "bigquery://projectid/location/dataset?disable_auth=false"}, + {"bigquery://projectid/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050", "bigquery://projectid/dataset?disable_auth=false"}, + {"bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true", "bigquery://projectid/location/dataset?disable_auth=true"}, + {"bigquery://bigquery:9050/test/dbmate_test?disable_auth=true", "bigquery://test/dbmate_test?disable_auth=true&endpoint=http://bigquery:9050"}, + {"bigquery://bigquery:9050/test/location/dbmate_test?disable_auth=true", "bigquery://test/location/dbmate_test?disable_auth=true&endpoint=http://bigquery:9050"}, } for _, c := range cases { - t.Run(c, func(t *testing.T) { - _, err := url.Parse(c) + t.Run(c.input, func(t *testing.T) { + u, err := url.Parse(c.input) require.NoError(t, err) + + actual := connectionString(u) + require.Equal(t, c.expected, actual) }) } } - func TestBigQueryCreateDropDatabase(t *testing.T) { - drv := testBigqueryDriver(t) + drv := testBigQueryDriver(t) // drop any existing database err := drv.DropDatabase() @@ -115,44 +114,47 @@ func TestBigQueryCreateDropDatabase(t *testing.T) { }() } -func TestBigqueryCreateAndInsertMigration(t *testing.T) { - drv := testBigqueryDriver(t) - drv.migrationsTableName = "test_migrations" +func TestBigQueryDatabaseExists(t *testing.T) { + drv := testBigQueryDriver(t) - // prepare database - db := prepTestDB(t) - defer dbutil.MustClose(db) + // drop any existing database + err := drv.DropDatabase() + require.NoError(t, err) - // create migrations table - err := drv.CreateMigrationsTable(db) + // DatabaseExists should return false + exists, err := drv.DatabaseExists() require.NoError(t, err) + require.Equal(t, false, exists) - // insert migration - err = drv.InsertMigration(db, "abc1") + // create database + err = drv.CreateDatabase() require.NoError(t, err) - err = drv.InsertMigration(db, "abc2") + + // DatabaseExists should return true + exists, err = drv.DatabaseExists() require.NoError(t, err) + require.Equal(t, true, exists) } func TestBigQueryCreateMigrationsTable(t *testing.T) { - drv := testBigqueryDriver(t) - drv.migrationsTableName = "test_migrations_1" + drv := testBigQueryDriver(t) + drv.migrationsTableName = "test_migrations" - db, err := getDbConnection(drv.databaseURL) - require.NoError(t, err) + db := prepTestBigQueryDB(t) defer dbutil.MustClose(db) // migrations table should not exist count := 0 - err = db.QueryRow("select count(*) from test_migrations_1").Scan(&count) + err := db.QueryRow("select count(*) from test_migrations").Scan(&count) require.Error(t, err) + require.Regexp(t, "Table not found: test_migrations", err.Error()) // create table err = drv.CreateMigrationsTable(db) require.NoError(t, err) // migrations table should exist - err = db.QueryRow("select count(*) from test_migrations_1").Scan(&count) + err = db.QueryRow("select count(*) from test_migrations").Scan(&count) require.NoError(t, err) // create table should be idempotent @@ -161,17 +163,16 @@ func TestBigQueryCreateMigrationsTable(t *testing.T) { } func TestBigQuerySelectMigrations(t *testing.T) { - drv := testBigqueryDriver(t) - drv.migrationsTableName = "test_migrations_2" + drv := testBigQueryDriver(t) + drv.migrationsTableName = "test_migrations" - db, err := getDbConnection(drv.databaseURL) - require.NoError(t, err) + db := prepTestBigQueryDB(t) defer dbutil.MustClose(db) - err = drv.CreateMigrationsTable(db) + err := drv.CreateMigrationsTable(db) require.NoError(t, err) - _, err = db.Exec(`insert into test_migrations_2 (version) + _, err = db.Exec(`insert into test_migrations (version) values ('abc2'), ('abc1'), ('abc3')`) require.NoError(t, err) @@ -190,18 +191,17 @@ func TestBigQuerySelectMigrations(t *testing.T) { } func TestBigQueryInsertMigration(t *testing.T) { - drv := testBigqueryDriver(t) - drv.migrationsTableName = "test_migrations_3" + drv := testBigQueryDriver(t) + drv.migrationsTableName = "test_migrations" - db, err := getDbConnection(drv.databaseURL) - require.NoError(t, err) + db := prepTestBigQueryDB(t) defer dbutil.MustClose(db) - err = drv.CreateMigrationsTable(db) + err := drv.CreateMigrationsTable(db) require.NoError(t, err) count := 0 - err = db.QueryRow("select count(*) from test_migrations_3").Scan(&count) + err = db.QueryRow("select count(*) from test_migrations").Scan(&count) require.NoError(t, err) require.Equal(t, 0, count) @@ -209,52 +209,44 @@ func TestBigQueryInsertMigration(t *testing.T) { err = drv.InsertMigration(db, "abc1") require.NoError(t, err) - err = db.QueryRow("select count(*) from test_migrations_3 where version = 'abc1'").Scan(&count) + err = db.QueryRow("select count(*) from test_migrations where version = 'abc1'"). + Scan(&count) require.NoError(t, err) require.Equal(t, 1, count) } func TestBigQueryDeleteMigration(t *testing.T) { - drv := testBigqueryDriver(t) - drv.migrationsTableName = "test_migrations_4" + drv := testBigQueryDriver(t) + drv.migrationsTableName = "test_migrations" - db, err := getDbConnection(drv.databaseURL) - require.NoError(t, err) + db := prepTestBigQueryDB(t) defer dbutil.MustClose(db) - err = drv.CreateMigrationsTable(db) + err := drv.CreateMigrationsTable(db) require.NoError(t, err) - _, err = db.Exec(`insert into test_migrations_4 (version) values ('abc1'), ('abc2')`) + _, err = db.Exec(`insert into test_migrations (version) + values ('abc1'), ('abc2')`) require.NoError(t, err) err = drv.DeleteMigration(db, "abc2") require.NoError(t, err) count := 0 - err = db.QueryRow("select count(*) from test_migrations_4").Scan(&count) + err = db.QueryRow("select count(*) from test_migrations").Scan(&count) require.NoError(t, err) require.Equal(t, 1, count) } -func TestBigQueryDatabaseExists(t *testing.T) { - drv := testBigqueryDriver(t) +func TestBigQueryPing(t *testing.T) { + drv := testBigQueryDriver(t) // drop any existing database err := drv.DropDatabase() require.NoError(t, err) - // DatabaseExists should return false - exists, err := drv.DatabaseExists() - require.NoError(t, err) - require.Equal(t, false, exists) - - // create database - err = drv.CreateDatabase() - require.NoError(t, err) - - // DatabaseExists should return true - exists, err = drv.DatabaseExists() - require.NoError(t, err) - require.Equal(t, true, exists) + // ping database + err = drv.Ping() + require.Error(t, err) + require.Contains(t, err.Error(), "dataset dbmate_test is not found") } diff --git a/pkg/driver/bigquery/test_cred.json b/pkg/driver/bigquery/test_cred.json deleted file mode 100644 index 0d8a7def..00000000 --- a/pkg/driver/bigquery/test_cred.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "service_account", - "project_id": "test-project", - "private_key_id": "someprivatekey", - "private_key": "\\n-----END PRIVATE KEY-----\n", - "client_email": "test@project.iam.gserviceaccount.com", - "client_id": "100000000000000000001", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dataset%40project.iam.gserviceaccount.com", - "universe_domain": "googleapis.com" -} From c36945a3ceadf6299666a38784e8e69af6b67490 Mon Sep 17 00:00:00 2001 From: mii9000 Date: Tue, 12 Mar 2024 14:44:31 +0600 Subject: [PATCH 07/17] uses reflection to use the client underneath SQL Connection --- pkg/driver/bigquery/bigquery.go | 510 ++++++++++++++++++--------- pkg/driver/bigquery/bigquery_test.go | 59 +++- 2 files changed, 399 insertions(+), 170 deletions(-) diff --git a/pkg/driver/bigquery/bigquery.go b/pkg/driver/bigquery/bigquery.go index e2a893cd..cb388061 100644 --- a/pkg/driver/bigquery/bigquery.go +++ b/pkg/driver/bigquery/bigquery.go @@ -6,51 +6,36 @@ import ( "fmt" "io" "net/url" + "reflect" "strconv" "strings" + "sync" + "unsafe" "cloud.google.com/go/bigquery" "google.golang.org/api/googleapi" "google.golang.org/api/iterator" - "google.golang.org/api/option" _ "gorm.io/driver/bigquery" // database/sql driver "github.com/amacneil/dbmate/v2/pkg/dbmate" "github.com/amacneil/dbmate/v2/pkg/dbutil" ) +func init() { + dbmate.RegisterDriver(NewDriver, "bigquery") +} + type Driver struct { migrationsTableName string - datasetID string - projectID string - log io.Writer databaseURL *url.URL - client *bigquery.Client - context *context.Context -} - -type bigQueryConfig struct { - projectID string - location string - dataSet string - scopes []string - endpoint string - disableAuth bool -} - -func init() { - dbmate.RegisterDriver(NewDriver, "bigquery") + log io.Writer } func NewDriver(config dbmate.DriverConfig) dbmate.Driver { - c, _ := configFromURI(config.DatabaseURL) - return &Driver{ migrationsTableName: config.MigrationsTableName, - datasetID: c.dataSet, - projectID: c.projectID, - log: config.Log, databaseURL: config.DatabaseURL, + log: config.Log, } } @@ -66,30 +51,89 @@ func (drv *Driver) CreateDatabase() error { return err } - if !exists { - err := drv.client.Dataset(drv.datasetID).Create(*drv.context, &bigquery.DatasetMetadata{}) + if exists { + return nil + } + + ctx := context.Background() + con, err := db.Conn(ctx) + if err != nil { + return err + } + defer con.Close() + + var wg sync.WaitGroup + wg.Add(1) + + err = con.Raw(func(driverConn any) error { + defer wg.Done() + + client := getClient(driverConn) + dataset := getDataset(driverConn) + err := client.Dataset(dataset).Create(ctx, &bigquery.DatasetMetadata{}) if err != nil { return err } + return nil + }) + + wg.Wait() + + if err != nil { + return err } return nil } -func (drv *Driver) CreateMigrationsTable(*sql.DB) error { - err := addBigQueryClientToDriver(drv) +func (drv *Driver) CreateMigrationsTable(db *sql.DB) error { + ctx := context.Background() + + con, err := db.Conn(ctx) if err != nil { return err } + defer con.Close() + + //check if the table exists + var exists bool + var wg sync.WaitGroup + wg.Add(1) + + err = con.Raw(func(driverConn any) error { + defer wg.Done() + + client := getClient(driverConn) + dataset := getDataset(driverConn) + exists, err = tableExists(client, dataset, drv.migrationsTableName) + if err != nil { + return err + } + + return nil + }) + + wg.Wait() - exists, err := tableExists(*drv.context, drv.client, drv.datasetID, drv.migrationsTableName) if err != nil { return err } - if !exists { - table := drv.client.Dataset(drv.datasetID).Table(drv.migrationsTableName) - err := table.Create(*drv.context, &bigquery.TableMetadata{ + //if already exists then return early + if exists { + return nil + } + + wg.Add(1) + + //if here then does not exist so create table + err = con.Raw(func(driverConn any) error { + defer wg.Done() + + client := getClient(driverConn) + dataset := getDataset(driverConn) + table := client.Dataset(dataset).Table(drv.migrationsTableName) + err := table.Create(ctx, &bigquery.TableMetadata{ Schema: bigquery.Schema{ { Name: "version", @@ -100,8 +144,13 @@ func (drv *Driver) CreateMigrationsTable(*sql.DB) error { if err != nil { return err } - return nil + }) + + wg.Wait() + + if err != nil { + return err } return nil @@ -114,21 +163,47 @@ func (drv *Driver) DatabaseExists() (bool, error) { } defer dbutil.MustClose(db) - it := drv.client.Datasets(*drv.context) - for { - dataset, err := it.Next() - if err == iterator.Done { - break - } - if err != nil { - return false, err - } - if dataset.DatasetID == drv.datasetID { - return true, nil + ctx := context.Background() + + con, err := db.Conn(ctx) + if err != nil { + return false, err + } + defer con.Close() + + var exists bool + var wg sync.WaitGroup + wg.Add(1) + + err = con.Raw(func(driverConn any) error { + defer wg.Done() + + client := getClient(driverConn) + datasetID := getDataset(driverConn) + it := client.Datasets(ctx) + for { + dataset, err := it.Next() + if err == iterator.Done { + exists = false + return nil + } + if err != nil { + return err + } + if dataset.DatasetID == datasetID { + exists = true + return nil + } } + }) + + wg.Wait() + + if err != nil { + return exists, err } - return false, nil + return exists, nil } func (drv *Driver) DropDatabase() error { @@ -143,17 +218,68 @@ func (drv *Driver) DropDatabase() error { return err } - if exists { - err := drv.client.Dataset(drv.datasetID).DeleteWithContents(*drv.context) + if !exists { + return nil + } + + ctx := context.Background() + + con, err := db.Conn(ctx) + if err != nil { + return err + } + defer con.Close() + + var wg sync.WaitGroup + wg.Add(1) + + err = con.Raw(func(driverConn any) error { + defer wg.Done() + + client := getClient(driverConn) + dataset := getDataset(driverConn) + err := client.Dataset(dataset).DeleteWithContents(ctx) if err != nil { return err } + return nil + }) + + wg.Wait() + + if err != nil { + return err } return nil } func (drv *Driver) DumpSchema(db *sql.DB) ([]byte, error) { + ctx := context.Background() + con, err := db.Conn(ctx) + if err != nil { + return nil, err + } + defer con.Close() + + var projectID, datasetID string + var wg sync.WaitGroup + wg.Add(1) + + err = con.Raw(func(driverConn any) error { + defer wg.Done() + + projectID = getProjectID(driverConn) + datasetID = getDataset(driverConn) + return nil + }) + + wg.Wait() + + if err != nil { + return nil, err + } + var script []byte query := fmt.Sprintf(` @@ -182,7 +308,7 @@ func (drv *Driver) DumpSchema(db *sql.DB) ([]byte, error) { FROM `+"`%s.%s.INFORMATION_SCHEMA.ROUTINES`"+` ORDER BY - order_type;`, drv.projectID, drv.datasetID, drv.projectID, drv.datasetID, drv.projectID, drv.datasetID) + order_type;`, projectID, datasetID, projectID, datasetID, projectID, datasetID) // Execute the query rows, err := db.Query(query) @@ -200,7 +326,7 @@ func (drv *Driver) DumpSchema(db *sql.DB) ([]byte, error) { } // Generate DDL for the object - ddl, err := generateDDL(db, drv.projectID, drv.datasetID, objectName, objectType) + ddl, err := generateDDL(db, projectID, datasetID, objectName, objectType) if err != nil { return nil, fmt.Errorf("error generating DDL for %s %s: %v", objectName, objectType, err) } @@ -217,13 +343,34 @@ func (drv *Driver) DumpSchema(db *sql.DB) ([]byte, error) { return script, nil } -func (drv *Driver) MigrationsTableExists(*sql.DB) (bool, error) { - err := addBigQueryClientToDriver(drv) +func (drv *Driver) MigrationsTableExists(db *sql.DB) (bool, error) { + var exists bool + + ctx := context.Background() + + con, err := db.Conn(ctx) if err != nil { - return false, err + return exists, err } + defer con.Close() + + var wg sync.WaitGroup + wg.Add(1) + + err = con.Raw(func(driverConn any) error { + defer wg.Done() + + client := getClient(driverConn) + dataset := getDataset(driverConn) + exists, err = tableExists(client, dataset, drv.migrationsTableName) + if err != nil { + return err + } + return nil + }) + + wg.Wait() - exists, err := tableExists(*drv.context, drv.client, drv.datasetID, drv.migrationsTableName) if err != nil { return exists, err } @@ -231,19 +378,89 @@ func (drv *Driver) MigrationsTableExists(*sql.DB) (bool, error) { return exists, nil } -func (drv *Driver) DeleteMigration(db dbutil.Transaction, version string) error { - query := fmt.Sprintf("DELETE FROM %s.%s WHERE version = '%s';", drv.datasetID, drv.migrationsTableName, version) - _, err := db.Exec(query) +func (drv *Driver) DeleteMigration(util dbutil.Transaction, version string) error { + db, err := drv.Open() + if err != nil { + return err + } + defer dbutil.MustClose(db) - return err + ctx := context.Background() + + con, err := db.Conn(ctx) + if err != nil { + return err + } + defer con.Close() + + var dataset string + var wg sync.WaitGroup + wg.Add(1) + + err = con.Raw(func(driverConn any) error { + defer wg.Done() + + dataset = getDataset(driverConn) + return nil + }) + + wg.Wait() + + if err != nil { + return err + } + + query := fmt.Sprintf("DELETE FROM %s.%s WHERE version = '%s';", dataset, drv.migrationsTableName, version) + _, err = util.Exec(query) + + if err != nil { + return err + } + + return nil } -func (drv *Driver) InsertMigration(db dbutil.Transaction, version string) error { +func (drv *Driver) InsertMigration(_ dbutil.Transaction, version string) error { + db, err := drv.Open() + if err != nil { + return err + } + defer dbutil.MustClose(db) + + ctx := context.Background() + con, err := db.Conn(ctx) + if err != nil { + return err + } + defer con.Close() + + var dataset string + var wg sync.WaitGroup + wg.Add(1) + + err = con.Raw(func(driverConn any) error { + defer wg.Done() + + dataset = getDataset(driverConn) + return nil + }) + + wg.Wait() + + if err != nil { + return err + } + queryTemplate := `INSERT INTO %s.%s (version) VALUES ('%s');` - queryString := fmt.Sprintf(queryTemplate, drv.datasetID, drv.migrationsTableName, version) + queryString := fmt.Sprintf(queryTemplate, dataset, drv.migrationsTableName, version) - _, err := db.Exec(queryString, version) - return err + _, err = db.Exec(queryString, version) + + if err != nil { + return err + } + + return nil } func (drv *Driver) Open() (*sql.DB, error) { @@ -253,11 +470,6 @@ func (drv *Driver) Open() (*sql.DB, error) { return nil, err } - err = addBigQueryClientToDriver(drv) - if err != nil { - return nil, err - } - return con, err } @@ -278,7 +490,31 @@ func (*Driver) QueryError(query string, err error) error { } func (drv *Driver) SelectMigrations(db *sql.DB, limit int) (map[string]bool, error) { - query := fmt.Sprintf("SELECT version FROM %s.%s ORDER BY version DESC", drv.datasetID, drv.migrationsTableName) + ctx := context.Background() + con, err := db.Conn(ctx) + if err != nil { + return nil, err + } + defer con.Close() + + var dataset string + var wg sync.WaitGroup + wg.Add(1) + + err = con.Raw(func(driverConn any) error { + defer wg.Done() + + dataset = getDataset(driverConn) + return nil + }) + + wg.Wait() + + if err != nil { + return nil, err + } + + query := fmt.Sprintf("SELECT version FROM %s.%s ORDER BY version DESC", dataset, drv.migrationsTableName) if limit >= 0 { query = fmt.Sprintf("%s limit %d", query, limit) } @@ -306,87 +542,6 @@ func (drv *Driver) SelectMigrations(db *sql.DB, limit int) (map[string]bool, err return migrations, nil } -func addBigQueryClientToDriver(drv *Driver) error { - config, err := configFromURI(drv.databaseURL) - if err != nil { - return err - } - - if drv.client == nil || drv.context == nil { - ctx := context.Background() - client, err := getClient(ctx, config) - if err != nil { - return err - } - - drv.client = client - drv.context = &ctx - } - - return nil -} - -func configFromURI(dbURL *url.URL) (*bigQueryConfig, error) { - invalidErrorMessage := "invalid connection string: %s" - - uri := dbURL.String() - - u, err := dbURL.Parse(uri) - if err != nil { - return nil, fmt.Errorf(invalidErrorMessage, uri) - } - - if u.Scheme != "bigquery" { - return nil, fmt.Errorf("invalid prefix, expected bigquery:// got: %s", uri) - } - - if u.Path == "" { - return nil, fmt.Errorf(invalidErrorMessage, uri) - } - - fields := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") - if len(fields) > 3 { - return nil, fmt.Errorf(invalidErrorMessage, uri) - } - - config := &bigQueryConfig{ - projectID: u.Hostname(), - dataSet: fields[len(fields)-1], - disableAuth: u.Query().Get("disable_auth") == "true", - } - - if u.Port() != "" { - config.endpoint = fmt.Sprintf("http://%s:%s", u.Hostname(), u.Port()) - config.projectID = fields[0] - if len(fields) == 3 { - config.location = fields[1] - } - } else { - if len(fields) == 2 { - config.location = fields[0] - } - } - - return config, nil -} - -func getClient(ctx context.Context, config *bigQueryConfig) (*bigquery.Client, error) { - opts := []option.ClientOption{option.WithScopes(config.scopes...)} - if config.endpoint != "" { - opts = append(opts, option.WithEndpoint(config.endpoint)) - } - if config.disableAuth { - opts = append(opts, option.WithoutAuthentication()) - } - - client, err := bigquery.NewClient(ctx, config.projectID, opts...) - if err != nil { - return nil, err - } - - return client, nil -} - func generateDDL(db *sql.DB, projectID, datasetID, objectName, objectType string) (string, error) { var ddl string @@ -515,9 +670,9 @@ func generateDDL(db *sql.DB, projectID, datasetID, objectName, objectType string } // Helper function to check whether a table exists or not in a dataset -func tableExists(ctx context.Context, client *bigquery.Client, datasetID, tableName string) (bool, error) { +func tableExists(client *bigquery.Client, datasetID, tableName string) (bool, error) { table := client.Dataset(datasetID).Table(tableName) - _, err := table.Metadata(ctx) + _, err := table.Metadata(context.Background()) if err == nil { return true, nil } @@ -527,18 +682,55 @@ func tableExists(ctx context.Context, client *bigquery.Client, datasetID, tableN return false, err } -func connectionString(url *url.URL) string { - c, _ := configFromURI(url) +func connectionString(u *url.URL) string { + var params, locationPath string + + fields := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") + projectID := u.Hostname() + dataset := fields[len(fields)-1] + disableAuth := u.Query().Get("disable_auth") == "true" - params := fmt.Sprintf("disable_auth=%s", strconv.FormatBool(c.disableAuth)) - if c.endpoint != "" { - params += fmt.Sprintf("&endpoint=%s", c.endpoint) + if disableAuth { + params += fmt.Sprintf("?disable_auth=%s", strconv.FormatBool(disableAuth)) } - var locationParam string - if c.location != "" { - locationParam = "/" + c.location + if u.Port() != "" { + if disableAuth { + params += "&" + } else { + params += "?" + } + params += "endpoint=" + url.QueryEscape(fmt.Sprintf("http://%s:%s", u.Hostname(), u.Port())) + projectID = fields[0] + if len(fields) == 3 { + locationPath = "/" + fields[1] + } + } else { + if len(fields) == 2 { + locationPath = "/" + fields[0] + } } - return fmt.Sprintf("bigquery://%s%s/%s?%s", c.projectID, locationParam, c.dataSet, params) + return fmt.Sprintf("bigquery://%s%s/%s%s", projectID, locationPath, dataset, params) +} + +func getClient(con any) *bigquery.Client { + value := reflect.ValueOf(con).Elem().FieldByName("client") + value = reflect.NewAt(value.Type(), unsafe.Pointer(value.UnsafeAddr())).Elem() + client := value.Interface().(*bigquery.Client) + return client +} + +func getConfigValue(con any, field string) reflect.Value { + connValue := reflect.ValueOf(con).Elem() + configField := connValue.FieldByName("config") + return configField.FieldByName(field) +} + +func getProjectID(con any) string { + return getConfigValue(con, "projectID").String() +} + +func getDataset(con any) string { + return getConfigValue(con, "dataSet").String() } diff --git a/pkg/driver/bigquery/bigquery_test.go b/pkg/driver/bigquery/bigquery_test.go index 417aadb8..4b021a97 100644 --- a/pkg/driver/bigquery/bigquery_test.go +++ b/pkg/driver/bigquery/bigquery_test.go @@ -1,23 +1,23 @@ package bigquery import ( + "context" "database/sql" "net/url" "os" + "reflect" "testing" + "unsafe" + "cloud.google.com/go/bigquery" "github.com/stretchr/testify/require" "github.com/amacneil/dbmate/v2/pkg/dbmate" "github.com/amacneil/dbmate/v2/pkg/dbutil" ) -func getURL() string { - return os.Getenv("BIGQUERY_TEST_URL") -} - func testBigQueryDriver(t *testing.T) *Driver { - u := dbutil.MustParseURL(getURL()) + u := dbutil.MustParseURL(os.Getenv("BIGQUERY_TEST_URL")) drv, err := dbmate.New(u).Driver() require.NoError(t, err) @@ -43,7 +43,7 @@ func prepTestBigQueryDB(t *testing.T) *sql.DB { } func TestGetDriver(t *testing.T) { - db := dbmate.New(dbutil.MustParseURL("bigquery://test/db_mate")) + db := dbmate.New(dbutil.MustParseURL("bigquery://")) drvInterface, err := db.Driver() require.NoError(t, err) @@ -54,17 +54,54 @@ func TestGetDriver(t *testing.T) { require.Equal(t, "schema_migrations", drv.migrationsTableName) } +func TestGetClient(t *testing.T) { + drv := testBigQueryDriver(t) + + db, err := drv.Open() + require.NoError(t, err) + defer dbutil.MustClose(db) + + conn, err := db.Conn(context.Background()) + require.NoError(t, err) + defer conn.Close() + + err = conn.Raw(func(driverConn any) error { + value := reflect.ValueOf(driverConn).Elem().FieldByName("client") + value = reflect.NewAt(value.Type(), unsafe.Pointer(value.UnsafeAddr())).Elem() + + client := value.Interface().(*bigquery.Client) + require.Equal(t, "test", client.Project()) + + connValue := reflect.ValueOf(driverConn).Elem() + configField := connValue.FieldByName("config") + + projectField := configField.FieldByName("projectID") + require.True(t, projectField.IsValid()) + require.Equal(t, "test", projectField.String()) + + datasetField := configField.FieldByName("dataSet") + require.True(t, datasetField.IsValid()) + require.Equal(t, "dbmate_test", datasetField.String()) + + return nil + }) + require.NoError(t, err) +} + func TestConnectionString(t *testing.T) { cases := []struct { input string expected string }{ - {"bigquery://projectid/dataset", "bigquery://projectid/dataset?disable_auth=false"}, - {"bigquery://projectid/location/dataset", "bigquery://projectid/location/dataset?disable_auth=false"}, - {"bigquery://projectid/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050", "bigquery://projectid/dataset?disable_auth=false"}, + {"bigquery://projectid/dataset", "bigquery://projectid/dataset"}, + {"bigquery://projectid/location/dataset", "bigquery://projectid/location/dataset"}, + {"bigquery://projectid/location/dataset?disable_auth=false", "bigquery://projectid/location/dataset"}, + {"bigquery://projectid/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050", "bigquery://projectid/dataset"}, {"bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true", "bigquery://projectid/location/dataset?disable_auth=true"}, - {"bigquery://bigquery:9050/test/dbmate_test?disable_auth=true", "bigquery://test/dbmate_test?disable_auth=true&endpoint=http://bigquery:9050"}, - {"bigquery://bigquery:9050/test/location/dbmate_test?disable_auth=true", "bigquery://test/location/dbmate_test?disable_auth=true&endpoint=http://bigquery:9050"}, + {"bigquery://bigquery:9050/test/dbmate_test?disable_auth=true", "bigquery://test/dbmate_test?disable_auth=true&endpoint=http%3A%2F%2Fbigquery%3A9050"}, + {"bigquery://0.0.0.0:9050/project/location/dataset?disable_auth=true", "bigquery://project/location/dataset?disable_auth=true&endpoint=http%3A%2F%2F0.0.0.0%3A9050"}, + {"bigquery://bigquery:9050/test/dbmate_test?disable_auth=false", "bigquery://test/dbmate_test?endpoint=http%3A%2F%2Fbigquery%3A9050"}, + {"bigquery://0.0.0.0:9050/project/location/dataset", "bigquery://project/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050"}, } for _, c := range cases { From 8c808c4ef841e76881108f884d03cfa852831c7e Mon Sep 17 00:00:00 2001 From: mii9000 Date: Tue, 12 Mar 2024 14:44:45 +0600 Subject: [PATCH 08/17] updates readme to add BigQuery usage --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index b4ddf60a..9bcc4a83 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ For a comparison between dbmate and other popular database schema migration tool - [MySQL](#mysql) - [SQLite](#sqlite) - [ClickHouse](#clickhouse) + - [BigQuery](#bigquery) - [Creating Migrations](#creating-migrations) - [Running Migrations](#running-migrations) - [Rolling Back Migrations](#rolling-back-migrations) @@ -287,6 +288,28 @@ DATABASE_URL="clickhouse://username:password@127.0.0.1:9000/database_name?on_clu [See other supported connection options](https://github.com/ClickHouse/clickhouse-go#dsn). +#### BigQuery +Follow the following format for `DATABASE_URL` when connecting to actual BigQuery in GCP: + +``` +bigquery://projectid/location/dataset?disable_auth=true +``` +`projectid` (mandatory) - Project ID + +`dataset` (mandatory) - Dataset Name within the Project + +`location` (optional) - Where Dataset is created + +`disable_auth` (optional) - Pass `true` to skip Authentication, use only for testing or connecting to emulator. + +*NOTE: Follow [this doc](https://cloud.google.com/docs/authentication/provide-credentials-adc) on how to set `GOOGLE_APPLICATION_CREDENTIALS` environment variable for proper Authentication* + +Follow the following format if trying to connect to a custom endpoint e.g. [BigQuery Emulator](https://github.com/goccy/bigquery-emulator) + +``` +bigquery://host:port/projectid/location/dataset +``` + ### Creating Migrations To create a new migration, run `dbmate new create_users_table`. You can name the migration anything you like. This will create a file `db/migrations/20151127184807_create_users_table.sql` in the current directory: From 03592d49c7bbf29a500dc5a753d8c256663b4c54 Mon Sep 17 00:00:00 2001 From: mii9000 Date: Wed, 13 Mar 2024 13:08:50 +0600 Subject: [PATCH 09/17] merges con.Raw() in a func and refactors connectionString() --- pkg/driver/bigquery/bigquery.go | 92 +++++++++++++++------------------ 1 file changed, 41 insertions(+), 51 deletions(-) diff --git a/pkg/driver/bigquery/bigquery.go b/pkg/driver/bigquery/bigquery.go index cb388061..d222753d 100644 --- a/pkg/driver/bigquery/bigquery.go +++ b/pkg/driver/bigquery/bigquery.go @@ -7,7 +7,6 @@ import ( "io" "net/url" "reflect" - "strconv" "strings" "sync" "unsafe" @@ -106,44 +105,27 @@ func (drv *Driver) CreateMigrationsTable(db *sql.DB) error { client := getClient(driverConn) dataset := getDataset(driverConn) exists, err = tableExists(client, dataset, drv.migrationsTableName) + if err != nil { return err } - return nil - }) - - wg.Wait() - - if err != nil { - return err - } - - //if already exists then return early - if exists { - return nil - } - - wg.Add(1) - - //if here then does not exist so create table - err = con.Raw(func(driverConn any) error { - defer wg.Done() - - client := getClient(driverConn) - dataset := getDataset(driverConn) - table := client.Dataset(dataset).Table(drv.migrationsTableName) - err := table.Create(ctx, &bigquery.TableMetadata{ - Schema: bigquery.Schema{ - { - Name: "version", - Type: bigquery.StringFieldType, + if !exists { + table := client.Dataset(dataset).Table(drv.migrationsTableName) + err := table.Create(ctx, &bigquery.TableMetadata{ + Schema: bigquery.Schema{ + { + Name: "version", + Type: bigquery.StringFieldType, + }, }, - }, - }) - if err != nil { - return err + }) + + if err != nil { + return err + } } + return nil }) @@ -683,35 +665,43 @@ func tableExists(client *bigquery.Client, datasetID, tableName string) (bool, er } func connectionString(u *url.URL) string { - var params, locationPath string + params := url.Values{} - fields := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") - projectID := u.Hostname() - dataset := fields[len(fields)-1] - disableAuth := u.Query().Get("disable_auth") == "true" + paths := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") - if disableAuth { - params += fmt.Sprintf("?disable_auth=%s", strconv.FormatBool(disableAuth)) + if u.Query().Get("disable_auth") == "true" { + params.Set("disable_auth", "true") } + formattedURL := &url.URL{} if u.Port() != "" { - if disableAuth { - params += "&" + formattedURL, _ = url.Parse(fmt.Sprintf("bigquery://%s", paths[0])) + params.Set("endpoint", fmt.Sprintf("http://%s:%s", u.Hostname(), u.Port())) + if len(paths) == 3 { + // bigquery://host:port/project/location/dataset + formattedURL.Path += "/" + paths[1] + formattedURL.Path += "/" + paths[2] } else { - params += "?" - } - params += "endpoint=" + url.QueryEscape(fmt.Sprintf("http://%s:%s", u.Hostname(), u.Port())) - projectID = fields[0] - if len(fields) == 3 { - locationPath = "/" + fields[1] + // bigquery://host:port/project/dataset + formattedURL.Path += "/" + paths[1] } } else { - if len(fields) == 2 { - locationPath = "/" + fields[0] + formattedURL, _ = url.Parse(fmt.Sprintf("bigquery://%s", u.Hostname())) + if len(paths) == 2 { + // bigquery://project/location/dataset + formattedURL.Path += "/" + paths[0] + formattedURL.Path += "/" + paths[1] + } else { + // bigquery://project/dataset + formattedURL.Path += "/" + paths[0] } } - return fmt.Sprintf("bigquery://%s%s/%s%s", projectID, locationPath, dataset, params) + if len(params) != 0 { + formattedURL.RawQuery = params.Encode() + } + + return formattedURL.String() } func getClient(con any) *bigquery.Client { From 9d535a942b0b1301924ae471f7b3066e47909b2a Mon Sep 17 00:00:00 2001 From: mii9000 Date: Thu, 14 Mar 2024 11:54:42 +0600 Subject: [PATCH 10/17] removes sync, waitgroups --- pkg/driver/bigquery/bigquery.go | 57 --------------------------------- 1 file changed, 57 deletions(-) diff --git a/pkg/driver/bigquery/bigquery.go b/pkg/driver/bigquery/bigquery.go index d222753d..917ec081 100644 --- a/pkg/driver/bigquery/bigquery.go +++ b/pkg/driver/bigquery/bigquery.go @@ -8,7 +8,6 @@ import ( "net/url" "reflect" "strings" - "sync" "unsafe" "cloud.google.com/go/bigquery" @@ -61,12 +60,7 @@ func (drv *Driver) CreateDatabase() error { } defer con.Close() - var wg sync.WaitGroup - wg.Add(1) - err = con.Raw(func(driverConn any) error { - defer wg.Done() - client := getClient(driverConn) dataset := getDataset(driverConn) err := client.Dataset(dataset).Create(ctx, &bigquery.DatasetMetadata{}) @@ -76,8 +70,6 @@ func (drv *Driver) CreateDatabase() error { return nil }) - wg.Wait() - if err != nil { return err } @@ -96,12 +88,8 @@ func (drv *Driver) CreateMigrationsTable(db *sql.DB) error { //check if the table exists var exists bool - var wg sync.WaitGroup - wg.Add(1) err = con.Raw(func(driverConn any) error { - defer wg.Done() - client := getClient(driverConn) dataset := getDataset(driverConn) exists, err = tableExists(client, dataset, drv.migrationsTableName) @@ -129,8 +117,6 @@ func (drv *Driver) CreateMigrationsTable(db *sql.DB) error { return nil }) - wg.Wait() - if err != nil { return err } @@ -154,12 +140,8 @@ func (drv *Driver) DatabaseExists() (bool, error) { defer con.Close() var exists bool - var wg sync.WaitGroup - wg.Add(1) err = con.Raw(func(driverConn any) error { - defer wg.Done() - client := getClient(driverConn) datasetID := getDataset(driverConn) it := client.Datasets(ctx) @@ -179,8 +161,6 @@ func (drv *Driver) DatabaseExists() (bool, error) { } }) - wg.Wait() - if err != nil { return exists, err } @@ -212,12 +192,7 @@ func (drv *Driver) DropDatabase() error { } defer con.Close() - var wg sync.WaitGroup - wg.Add(1) - err = con.Raw(func(driverConn any) error { - defer wg.Done() - client := getClient(driverConn) dataset := getDataset(driverConn) err := client.Dataset(dataset).DeleteWithContents(ctx) @@ -227,8 +202,6 @@ func (drv *Driver) DropDatabase() error { return nil }) - wg.Wait() - if err != nil { return err } @@ -245,19 +218,13 @@ func (drv *Driver) DumpSchema(db *sql.DB) ([]byte, error) { defer con.Close() var projectID, datasetID string - var wg sync.WaitGroup - wg.Add(1) err = con.Raw(func(driverConn any) error { - defer wg.Done() - projectID = getProjectID(driverConn) datasetID = getDataset(driverConn) return nil }) - wg.Wait() - if err != nil { return nil, err } @@ -336,12 +303,7 @@ func (drv *Driver) MigrationsTableExists(db *sql.DB) (bool, error) { } defer con.Close() - var wg sync.WaitGroup - wg.Add(1) - err = con.Raw(func(driverConn any) error { - defer wg.Done() - client := getClient(driverConn) dataset := getDataset(driverConn) exists, err = tableExists(client, dataset, drv.migrationsTableName) @@ -351,8 +313,6 @@ func (drv *Driver) MigrationsTableExists(db *sql.DB) (bool, error) { return nil }) - wg.Wait() - if err != nil { return exists, err } @@ -376,18 +336,13 @@ func (drv *Driver) DeleteMigration(util dbutil.Transaction, version string) erro defer con.Close() var dataset string - var wg sync.WaitGroup - wg.Add(1) err = con.Raw(func(driverConn any) error { - defer wg.Done() dataset = getDataset(driverConn) return nil }) - wg.Wait() - if err != nil { return err } @@ -417,18 +372,12 @@ func (drv *Driver) InsertMigration(_ dbutil.Transaction, version string) error { defer con.Close() var dataset string - var wg sync.WaitGroup - wg.Add(1) err = con.Raw(func(driverConn any) error { - defer wg.Done() - dataset = getDataset(driverConn) return nil }) - wg.Wait() - if err != nil { return err } @@ -480,18 +429,12 @@ func (drv *Driver) SelectMigrations(db *sql.DB, limit int) (map[string]bool, err defer con.Close() var dataset string - var wg sync.WaitGroup - wg.Add(1) err = con.Raw(func(driverConn any) error { - defer wg.Done() - dataset = getDataset(driverConn) return nil }) - wg.Wait() - if err != nil { return nil, err } From 3f8feb4518b1deffad6fa5b129a6e41ec8a31630 Mon Sep 17 00:00:00 2001 From: mii9000 Date: Thu, 14 Mar 2024 12:23:21 +0600 Subject: [PATCH 11/17] drops all params when connecting to actual bq --- README.md | 10 +++--- pkg/driver/bigquery/bigquery.go | 50 ++++++++++++++-------------- pkg/driver/bigquery/bigquery_test.go | 3 +- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 9bcc4a83..ca1e25bf 100644 --- a/README.md +++ b/README.md @@ -292,24 +292,24 @@ DATABASE_URL="clickhouse://username:password@127.0.0.1:9000/database_name?on_clu Follow the following format for `DATABASE_URL` when connecting to actual BigQuery in GCP: ``` -bigquery://projectid/location/dataset?disable_auth=true +bigquery://projectid/location/dataset ``` `projectid` (mandatory) - Project ID -`dataset` (mandatory) - Dataset Name within the Project +`dataset` (mandatory) - Dataset name within the Project `location` (optional) - Where Dataset is created -`disable_auth` (optional) - Pass `true` to skip Authentication, use only for testing or connecting to emulator. - *NOTE: Follow [this doc](https://cloud.google.com/docs/authentication/provide-credentials-adc) on how to set `GOOGLE_APPLICATION_CREDENTIALS` environment variable for proper Authentication* Follow the following format if trying to connect to a custom endpoint e.g. [BigQuery Emulator](https://github.com/goccy/bigquery-emulator) ``` -bigquery://host:port/projectid/location/dataset +bigquery://host:port/projectid/location/dataset?disable_auth=true ``` +`disable_auth` (optional) - Pass `true` to skip Authentication, use only for testing and connecting to emulator. + ### Creating Migrations To create a new migration, run `dbmate new create_users_table`. You can name the migration anything you like. This will create a file `db/migrations/20151127184807_create_users_table.sql` in the current directory: diff --git a/pkg/driver/bigquery/bigquery.go b/pkg/driver/bigquery/bigquery.go index 917ec081..e5f86073 100644 --- a/pkg/driver/bigquery/bigquery.go +++ b/pkg/driver/bigquery/bigquery.go @@ -608,43 +608,43 @@ func tableExists(client *bigquery.Client, datasetID, tableName string) (bool, er } func connectionString(u *url.URL) string { - params := url.Values{} - - paths := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") + //if connecting to emulator with host:port format + if u.Port() != "" { + paths := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") - if u.Query().Get("disable_auth") == "true" { - params.Set("disable_auth", "true") - } + newURL := &url.URL{ + Scheme: u.Scheme, + Host: paths[0], + } - formattedURL := &url.URL{} - if u.Port() != "" { - formattedURL, _ = url.Parse(fmt.Sprintf("bigquery://%s", paths[0])) + params := url.Values{} + if u.Query().Get("disable_auth") == "true" { + params.Set("disable_auth", "true") + } params.Set("endpoint", fmt.Sprintf("http://%s:%s", u.Hostname(), u.Port())) + if len(paths) == 3 { // bigquery://host:port/project/location/dataset - formattedURL.Path += "/" + paths[1] - formattedURL.Path += "/" + paths[2] + newURL.Path += "/" + paths[1] + newURL.Path += "/" + paths[2] } else { // bigquery://host:port/project/dataset - formattedURL.Path += "/" + paths[1] - } - } else { - formattedURL, _ = url.Parse(fmt.Sprintf("bigquery://%s", u.Hostname())) - if len(paths) == 2 { - // bigquery://project/location/dataset - formattedURL.Path += "/" + paths[0] - formattedURL.Path += "/" + paths[1] - } else { - // bigquery://project/dataset - formattedURL.Path += "/" + paths[0] + newURL.Path += "/" + paths[1] } + + newURL.RawQuery = params.Encode() + + return newURL.String() } - if len(params) != 0 { - formattedURL.RawQuery = params.Encode() + //connecting to GCP BigQuery, drop all query strings + newURL := &url.URL{ + Scheme: u.Scheme, + Host: u.Host, + Path: u.Path, } - return formattedURL.String() + return newURL.String() } func getClient(con any) *bigquery.Client { diff --git a/pkg/driver/bigquery/bigquery_test.go b/pkg/driver/bigquery/bigquery_test.go index 4b021a97..cdf27ac4 100644 --- a/pkg/driver/bigquery/bigquery_test.go +++ b/pkg/driver/bigquery/bigquery_test.go @@ -96,8 +96,9 @@ func TestConnectionString(t *testing.T) { {"bigquery://projectid/dataset", "bigquery://projectid/dataset"}, {"bigquery://projectid/location/dataset", "bigquery://projectid/location/dataset"}, {"bigquery://projectid/location/dataset?disable_auth=false", "bigquery://projectid/location/dataset"}, + {"bigquery://projectid/location/dataset?disable_auth=true", "bigquery://projectid/location/dataset"}, {"bigquery://projectid/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050", "bigquery://projectid/dataset"}, - {"bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true", "bigquery://projectid/location/dataset?disable_auth=true"}, + {"bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true", "bigquery://projectid/location/dataset"}, {"bigquery://bigquery:9050/test/dbmate_test?disable_auth=true", "bigquery://test/dbmate_test?disable_auth=true&endpoint=http%3A%2F%2Fbigquery%3A9050"}, {"bigquery://0.0.0.0:9050/project/location/dataset?disable_auth=true", "bigquery://project/location/dataset?disable_auth=true&endpoint=http%3A%2F%2F0.0.0.0%3A9050"}, {"bigquery://bigquery:9050/test/dbmate_test?disable_auth=false", "bigquery://test/dbmate_test?endpoint=http%3A%2F%2Fbigquery%3A9050"}, From 03d846efa2d4971691e40f2fa55adaf8d42a6118 Mon Sep 17 00:00:00 2001 From: mii9000 Date: Thu, 14 Mar 2024 12:35:19 +0600 Subject: [PATCH 12/17] adds ping success and migration exists tests --- pkg/driver/bigquery/bigquery_test.go | 32 +++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/pkg/driver/bigquery/bigquery_test.go b/pkg/driver/bigquery/bigquery_test.go index cdf27ac4..a1907171 100644 --- a/pkg/driver/bigquery/bigquery_test.go +++ b/pkg/driver/bigquery/bigquery_test.go @@ -276,7 +276,7 @@ func TestBigQueryDeleteMigration(t *testing.T) { require.Equal(t, 1, count) } -func TestBigQueryPing(t *testing.T) { +func TestBigQueryPingError(t *testing.T) { drv := testBigQueryDriver(t) // drop any existing database @@ -288,3 +288,33 @@ func TestBigQueryPing(t *testing.T) { require.Error(t, err) require.Contains(t, err.Error(), "dataset dbmate_test is not found") } + +func TestBigQueryPingSuccess(t *testing.T) { + drv := testBigQueryDriver(t) + + db := prepTestBigQueryDB(t) + defer dbutil.MustClose(db) + + // ping database + err := drv.Ping() + require.NoError(t, err) +} + +func TestPostgresMigrationsTableExists(t *testing.T) { + drv := testBigQueryDriver(t) + drv.migrationsTableName = "test_migrations" + + db := prepTestBigQueryDB(t) + defer dbutil.MustClose(db) + + exists, err := drv.MigrationsTableExists(db) + require.NoError(t, err) + require.Equal(t, false, exists) + + err = drv.CreateMigrationsTable(db) + require.NoError(t, err) + + exists, err = drv.MigrationsTableExists(db) + require.NoError(t, err) + require.Equal(t, true, exists) +} From 7e1ed74f175243538d42a09ed10f3d156db27fd1 Mon Sep 17 00:00:00 2001 From: mii9000 Date: Fri, 15 Mar 2024 01:59:43 +0600 Subject: [PATCH 13/17] fixes lint issue --- pkg/driver/bigquery/bigquery.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/driver/bigquery/bigquery.go b/pkg/driver/bigquery/bigquery.go index e5f86073..ff2dcfce 100644 --- a/pkg/driver/bigquery/bigquery.go +++ b/pkg/driver/bigquery/bigquery.go @@ -338,7 +338,6 @@ func (drv *Driver) DeleteMigration(util dbutil.Transaction, version string) erro var dataset string err = con.Raw(func(driverConn any) error { - dataset = getDataset(driverConn) return nil }) From 16de878fcceb587165a913021044e40cca368db2 Mon Sep 17 00:00:00 2001 From: mii9000 Date: Fri, 15 Mar 2024 16:00:34 +0600 Subject: [PATCH 14/17] fixes creating dataset in the defined location from connection string --- docker-compose.yml | 2 +- pkg/driver/bigquery/bigquery.go | 9 ++++++++- pkg/driver/bigquery/bigquery_test.go | 4 ++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e2487bd4..ba02ca96 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: MYSQL_TEST_URL: mysql://root:root@mysql/dbmate_test POSTGRES_TEST_URL: postgres://postgres:postgres@postgres/dbmate_test?sslmode=disable SQLITE_TEST_URL: sqlite3:/tmp/dbmate_test.sqlite3 - BIGQUERY_TEST_URL: bigquery://bigquery:9050/test/dbmate_test?disable_auth=true + BIGQUERY_TEST_URL: bigquery://bigquery:9050/test/asia-southeast1/dbmate_test?disable_auth=true dbmate: build: diff --git a/pkg/driver/bigquery/bigquery.go b/pkg/driver/bigquery/bigquery.go index ff2dcfce..483f170d 100644 --- a/pkg/driver/bigquery/bigquery.go +++ b/pkg/driver/bigquery/bigquery.go @@ -63,7 +63,10 @@ func (drv *Driver) CreateDatabase() error { err = con.Raw(func(driverConn any) error { client := getClient(driverConn) dataset := getDataset(driverConn) - err := client.Dataset(dataset).Create(ctx, &bigquery.DatasetMetadata{}) + location := getLocation(driverConn) + err := client.Dataset(dataset).Create(ctx, &bigquery.DatasetMetadata{ + Location: location, + }) if err != nil { return err } @@ -666,3 +669,7 @@ func getProjectID(con any) string { func getDataset(con any) string { return getConfigValue(con, "dataSet").String() } + +func getLocation(con any) string { + return getConfigValue(con, "location").String() +} diff --git a/pkg/driver/bigquery/bigquery_test.go b/pkg/driver/bigquery/bigquery_test.go index a1907171..8927b82a 100644 --- a/pkg/driver/bigquery/bigquery_test.go +++ b/pkg/driver/bigquery/bigquery_test.go @@ -83,6 +83,10 @@ func TestGetClient(t *testing.T) { require.True(t, datasetField.IsValid()) require.Equal(t, "dbmate_test", datasetField.String()) + locationField := configField.FieldByName("location") + require.True(t, locationField.IsValid()) + require.Equal(t, "asia-southeast1", locationField.String()) + return nil }) require.NoError(t, err) From ebbcd8ade9d00003b6017a34801ed46f107ea568 Mon Sep 17 00:00:00 2001 From: mii9000 Date: Sat, 16 Mar 2024 19:49:29 +0600 Subject: [PATCH 15/17] fixes test name --- pkg/driver/bigquery/bigquery_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/driver/bigquery/bigquery_test.go b/pkg/driver/bigquery/bigquery_test.go index 8927b82a..ecaed53a 100644 --- a/pkg/driver/bigquery/bigquery_test.go +++ b/pkg/driver/bigquery/bigquery_test.go @@ -304,7 +304,7 @@ func TestBigQueryPingSuccess(t *testing.T) { require.NoError(t, err) } -func TestPostgresMigrationsTableExists(t *testing.T) { +func TestBigQueryMigrationsTableExists(t *testing.T) { drv := testBigQueryDriver(t) drv.migrationsTableName = "test_migrations" From 5260cb9b9cb32184e71ca7fcd2c92a9cc465e0c5 Mon Sep 17 00:00:00 2001 From: Dossy Shiobara Date: Mon, 18 Mar 2024 20:14:11 -0400 Subject: [PATCH 16/17] Changes for Google BigQuery driver. * Specify BigQuery API endpoint using query param again. * Add instructions for adding a Google Cloud Project Service Account for testing against actual Google BigQuery. * Add BigQuery-compatible migrations as test fixtures. * Change `con` to `conn` to be consistent with the `sql.Conn` type name and `database/sql` example code naming convention, and spell out `driverConn` where it's used to clearly distinguish between the `sql.Conn` and the underlying driver connection type. * Introduce function to return driver connection's entire `bigQueryConfig` instance, rather than having separate functions for each field within that type. * Simplify `DumpSchema()` using the BigQuery-provided DDL instead of generating it ourselves. * Revert implementation that required dbmate's bigquery-schemed URLs to pass the project ID in the path portion of the URL instead of the hostname portion of the URL, and instead just pass through the URL to the underlying driver as-is. * Add tests that can selectively run against the actual Google BigQuery service, for functionality that is not yet implemented in the emulator. --- docker-compose.yml | 2 +- fixtures/bigquery/.gitignore | 1 + fixtures/bigquery/README.md | 49 +++ .../20151129054053_test_migration.sql | 9 + .../migrations/20200227231541_test_posts.sql | 8 + pkg/driver/bigquery/bigquery.go | 392 ++++++++---------- pkg/driver/bigquery/bigquery_test.go | 127 ++++-- 7 files changed, 333 insertions(+), 255 deletions(-) create mode 100644 fixtures/bigquery/.gitignore create mode 100644 fixtures/bigquery/README.md create mode 100644 fixtures/bigquery/migrations/20151129054053_test_migration.sql create mode 100644 fixtures/bigquery/migrations/20200227231541_test_posts.sql diff --git a/docker-compose.yml b/docker-compose.yml index ba02ca96..92ca61b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: MYSQL_TEST_URL: mysql://root:root@mysql/dbmate_test POSTGRES_TEST_URL: postgres://postgres:postgres@postgres/dbmate_test?sslmode=disable SQLITE_TEST_URL: sqlite3:/tmp/dbmate_test.sqlite3 - BIGQUERY_TEST_URL: bigquery://bigquery:9050/test/asia-southeast1/dbmate_test?disable_auth=true + BIGQUERY_TEST_URL: bigquery://test/us-east5/dbmate_test?disable_auth=true&endpoint=http%3A%2F%2Fbigquery%3A9050 dbmate: build: diff --git a/fixtures/bigquery/.gitignore b/fixtures/bigquery/.gitignore new file mode 100644 index 00000000..83f6e397 --- /dev/null +++ b/fixtures/bigquery/.gitignore @@ -0,0 +1 @@ +credentials.json diff --git a/fixtures/bigquery/README.md b/fixtures/bigquery/README.md new file mode 100644 index 00000000..de25c5dd --- /dev/null +++ b/fixtures/bigquery/README.md @@ -0,0 +1,49 @@ +# Google BigQuery Test Fixtures + +## Creating a service account for testing + +From the `dbmate` top-level directory: + +```sh +$ PROJECT_ID=your-google-cloud-project-id +$ LOCATION=us-east5 +$ DATASET=test_dataset +$ SERVICE_ACCOUNT=dbmate-test-sa + +$ gcloud auth login + +$ gcloud iam service-accounts create $SERVICE_ACCOUNT + +$ gcloud projects add-iam-policy-binding $PROJECT_ID \ + --role="roles/bigquery.dataEditor" \ + --member=serviceAccount:${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com + +$ gcloud projects add-iam-policy-binding $PROJECT_ID \ + --role="roles/bigquery.jobUser" \ + --member=serviceAccount:${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com + +$ gcloud iam service-accounts keys create \ + fixtures/bigquery/credentials.json \ + --iam-account=${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com + +## WARNING: Only do this on a private machine, as anyone else with +## access to the system will also be able to read the credentials file's +## contents once it's made world-readable and use it to access Google as +## the service account. This is necessary for dbmate running as root +## inside the Docker container to be able to read the file, though. + +$ chmod a+r fixtures/bigquery/credentials.json + +$ docker compose run --rm dev + +## The rest of these commands should be executed from inside the Docker +## container: + +$ make build + +$ make test \ + GOOGLE_APPLICATION_CREDENTIALS=/src/fixtures/bigquery/credentials.json \ + GOOGLE_BIGQUERY_TEST_URL=bigquery://$PROJECT_ID/$LOCATION/$DATASET \ + FLAGS+="-count 1 -v ./pkg/driver/bigquery #" + +``` diff --git a/fixtures/bigquery/migrations/20151129054053_test_migration.sql b/fixtures/bigquery/migrations/20151129054053_test_migration.sql new file mode 100644 index 00000000..e93dce59 --- /dev/null +++ b/fixtures/bigquery/migrations/20151129054053_test_migration.sql @@ -0,0 +1,9 @@ +-- migrate:up +create table users ( + id int64, + name string +); +insert into users (id, name) values (1, 'alice'); + +-- migrate:down +drop table users; diff --git a/fixtures/bigquery/migrations/20200227231541_test_posts.sql b/fixtures/bigquery/migrations/20200227231541_test_posts.sql new file mode 100644 index 00000000..7bdc88b2 --- /dev/null +++ b/fixtures/bigquery/migrations/20200227231541_test_posts.sql @@ -0,0 +1,8 @@ +-- migrate:up +create table posts ( + id int64, + name string +); + +-- migrate:down +drop table posts; diff --git a/pkg/driver/bigquery/bigquery.go b/pkg/driver/bigquery/bigquery.go index 483f170d..2306882d 100644 --- a/pkg/driver/bigquery/bigquery.go +++ b/pkg/driver/bigquery/bigquery.go @@ -1,6 +1,7 @@ package bigquery import ( + "bytes" "context" "database/sql" "fmt" @@ -54,77 +55,51 @@ func (drv *Driver) CreateDatabase() error { } ctx := context.Background() - con, err := db.Conn(ctx) + conn, err := db.Conn(ctx) if err != nil { return err } - defer con.Close() + defer conn.Close() - err = con.Raw(func(driverConn any) error { + return conn.Raw(func(driverConn any) error { client := getClient(driverConn) - dataset := getDataset(driverConn) - location := getLocation(driverConn) - err := client.Dataset(dataset).Create(ctx, &bigquery.DatasetMetadata{ - Location: location, + config := getConfig(driverConn) + + return client.Dataset(config.dataSet).Create(ctx, &bigquery.DatasetMetadata{ + Location: config.location, }) - if err != nil { - return err - } - return nil }) - - if err != nil { - return err - } - - return nil } func (drv *Driver) CreateMigrationsTable(db *sql.DB) error { ctx := context.Background() - - con, err := db.Conn(ctx) + conn, err := db.Conn(ctx) if err != nil { return err } - defer con.Close() + defer conn.Close() - //check if the table exists - var exists bool - - err = con.Raw(func(driverConn any) error { + return conn.Raw(func(driverConn any) error { client := getClient(driverConn) - dataset := getDataset(driverConn) - exists, err = tableExists(client, dataset, drv.migrationsTableName) + config := getConfig(driverConn) + exists, err := tableExists(client, config.dataSet, drv.migrationsTableName) if err != nil { return err } - - if !exists { - table := client.Dataset(dataset).Table(drv.migrationsTableName) - err := table.Create(ctx, &bigquery.TableMetadata{ - Schema: bigquery.Schema{ - { - Name: "version", - Type: bigquery.StringFieldType, - }, - }, - }) - - if err != nil { - return err - } + if exists { + return nil } - return nil + return client.Dataset(config.dataSet).Table(drv.migrationsTableName).Create(ctx, &bigquery.TableMetadata{ + Schema: bigquery.Schema{ + { + Name: "version", + Type: bigquery.StringFieldType, + }, + }, + }) }) - - if err != nil { - return err - } - - return nil } func (drv *Driver) DatabaseExists() (bool, error) { @@ -135,18 +110,17 @@ func (drv *Driver) DatabaseExists() (bool, error) { defer dbutil.MustClose(db) ctx := context.Background() - - con, err := db.Conn(ctx) + conn, err := db.Conn(ctx) if err != nil { return false, err } - defer con.Close() + defer conn.Close() var exists bool - - err = con.Raw(func(driverConn any) error { + err = conn.Raw(func(driverConn any) error { client := getClient(driverConn) - datasetID := getDataset(driverConn) + config := getConfig(driverConn) + it := client.Datasets(ctx) for { dataset, err := it.Next() @@ -157,18 +131,14 @@ func (drv *Driver) DatabaseExists() (bool, error) { if err != nil { return err } - if dataset.DatasetID == datasetID { + if dataset.DatasetID == config.dataSet { exists = true return nil } } }) - if err != nil { - return exists, err - } - - return exists, nil + return exists, err } func (drv *Driver) DropDatabase() error { @@ -182,85 +152,49 @@ func (drv *Driver) DropDatabase() error { if err != nil { return err } - if !exists { return nil } ctx := context.Background() - - con, err := db.Conn(ctx) + conn, err := db.Conn(ctx) if err != nil { return err } - defer con.Close() + defer conn.Close() - err = con.Raw(func(driverConn any) error { + return conn.Raw(func(driverConn any) error { client := getClient(driverConn) - dataset := getDataset(driverConn) - err := client.Dataset(dataset).DeleteWithContents(ctx) - if err != nil { - return err - } - return nil - }) + config := getConfig(driverConn) - if err != nil { - return err - } - - return nil + return client.Dataset(config.dataSet).DeleteWithContents(ctx) + }) } -func (drv *Driver) DumpSchema(db *sql.DB) ([]byte, error) { - ctx := context.Background() - con, err := db.Conn(ctx) - if err != nil { - return nil, err - } - defer con.Close() - - var projectID, datasetID string - - err = con.Raw(func(driverConn any) error { - projectID = getProjectID(driverConn) - datasetID = getDataset(driverConn) - return nil - }) +func (drv *Driver) schemaDump(db *sql.DB) ([]byte, error) { + // build schema migrations table data + var buf bytes.Buffer + buf.WriteString("\n--\n-- Database schema\n--\n\n") + config, err := drv.getConfig(db) if err != nil { return nil, err } - var script []byte - - query := fmt.Sprintf(` - SELECT - table_name, - table_type, - 1 AS order_type - FROM - `+"`%s.%s.INFORMATION_SCHEMA.TABLES`"+` - WHERE - table_type = 'BASE TABLE' - UNION ALL - SELECT - table_name, - table_type, - 2 AS order_type - FROM - `+"`%s.%s.INFORMATION_SCHEMA.TABLES`"+` - WHERE - table_type = 'VIEW' + query := fmt.Sprintf( + `SELECT table_name AS object_name, 'TABLE' AS object_type, ddl + FROM `+"`%s.%s.INFORMATION_SCHEMA.TABLES`"+` UNION ALL - SELECT - routine_name AS table_name, - 'FUNCTION' AS table_type, - 3 AS order_type - FROM - `+"`%s.%s.INFORMATION_SCHEMA.ROUTINES`"+` - ORDER BY - order_type;`, projectID, datasetID, projectID, datasetID, projectID, datasetID) + SELECT routine_name AS object_name, 'FUNCTION' AS object_type, ddl + FROM `+"`%s.%s.INFORMATION_SCHEMA.ROUTINES`"+` + ORDER BY CASE object_type + WHEN 'TABLE' THEN 1 + WHEN 'FUNCTION' THEN 2 + ELSE 3 + END;`, + config.projectID, config.dataSet, + config.projectID, config.dataSet, + ) // Execute the query rows, err := db.Query(query) @@ -271,51 +205,78 @@ func (drv *Driver) DumpSchema(db *sql.DB) ([]byte, error) { // Iterate over the results and generate DDL for each object for rows.Next() { - var objectName, objectType string - var orderType int - if err := rows.Scan(&objectName, &objectType, &orderType); err != nil { + var objectName, objectType, ddl string + if err := rows.Scan(&objectName, &objectType, &ddl); err != nil { return nil, fmt.Errorf("error scanning object: %v", err) } - // Generate DDL for the object - ddl, err := generateDDL(db, projectID, datasetID, objectName, objectType) - if err != nil { - return nil, fmt.Errorf("error generating DDL for %s %s: %v", objectName, objectType, err) - } - - // Append the DDL to the script - script = append(script, []byte(ddl)...) - script = append(script, []byte("\n\n")...) + buf.WriteString(ddl + "\n") } if err := rows.Err(); err != nil { return nil, fmt.Errorf("error iterating objects: %v", err) } - return script, nil + return buf.Bytes(), nil } -func (drv *Driver) MigrationsTableExists(db *sql.DB) (bool, error) { - var exists bool +func (drv *Driver) schemaMigrationsDump(db *sql.DB) ([]byte, error) { + migrationsTable := drv.migrationsTableName - ctx := context.Background() + // load applied migrations + migrations, err := dbutil.QueryColumn(db, + fmt.Sprintf("select version from %s order by version asc", migrationsTable)) + if err != nil { + return nil, err + } + + // build schema migrations table data + var buf bytes.Buffer + buf.WriteString("\n--\n-- Dbmate schema migrations\n--\n\n") + + if len(migrations) > 0 { + buf.WriteString( + fmt.Sprintf("INSERT INTO %s (version) VALUES\n ('", migrationsTable) + + strings.Join(migrations, "'),\n ('") + + "');\n") + } - con, err := db.Conn(ctx) + return buf.Bytes(), nil +} + +func (drv *Driver) DumpSchema(db *sql.DB) ([]byte, error) { + schema, err := drv.schemaDump(db) if err != nil { - return exists, err + return nil, err + } + + migrations, err := drv.schemaMigrationsDump(db) + if err != nil { + return nil, err + } + + return append(schema, migrations...), nil + +} + +func (drv *Driver) MigrationsTableExists(db *sql.DB) (bool, error) { + ctx := context.Background() + conn, err := db.Conn(ctx) + if err != nil { + return false, err } - defer con.Close() + defer conn.Close() - err = con.Raw(func(driverConn any) error { + var exists bool + err = conn.Raw(func(driverConn any) error { client := getClient(driverConn) - dataset := getDataset(driverConn) - exists, err = tableExists(client, dataset, drv.migrationsTableName) + config := getConfig(driverConn) + exists, err = tableExists(client, config.dataSet, drv.migrationsTableName) if err != nil { return err } return nil }) - if err != nil { return exists, err } @@ -330,28 +291,13 @@ func (drv *Driver) DeleteMigration(util dbutil.Transaction, version string) erro } defer dbutil.MustClose(db) - ctx := context.Background() - - con, err := db.Conn(ctx) + config, err := drv.getConfig(db) if err != nil { return err } - defer con.Close() - var dataset string - - err = con.Raw(func(driverConn any) error { - dataset = getDataset(driverConn) - return nil - }) - - if err != nil { - return err - } - - query := fmt.Sprintf("DELETE FROM %s.%s WHERE version = '%s';", dataset, drv.migrationsTableName, version) + query := fmt.Sprintf("DELETE FROM %s.%s WHERE version = '%s';", config.dataSet, drv.migrationsTableName, version) _, err = util.Exec(query) - if err != nil { return err } @@ -366,29 +312,14 @@ func (drv *Driver) InsertMigration(_ dbutil.Transaction, version string) error { } defer dbutil.MustClose(db) - ctx := context.Background() - con, err := db.Conn(ctx) - if err != nil { - return err - } - defer con.Close() - - var dataset string - - err = con.Raw(func(driverConn any) error { - dataset = getDataset(driverConn) - return nil - }) - + config, err := drv.getConfig(db) if err != nil { return err } queryTemplate := `INSERT INTO %s.%s (version) VALUES ('%s');` - queryString := fmt.Sprintf(queryTemplate, dataset, drv.migrationsTableName, version) - + queryString := fmt.Sprintf(queryTemplate, config.dataSet, drv.migrationsTableName, version) _, err = db.Exec(queryString, version) - if err != nil { return err } @@ -397,13 +328,7 @@ func (drv *Driver) InsertMigration(_ dbutil.Transaction, version string) error { } func (drv *Driver) Open() (*sql.DB, error) { - connString := connectionString(drv.databaseURL) - con, err := sql.Open("bigquery", connString) - if err != nil { - return nil, err - } - - return con, err + return sql.Open("bigquery", connectionString(drv.databaseURL)) } func (drv *Driver) Ping() error { @@ -413,9 +338,7 @@ func (drv *Driver) Ping() error { } defer dbutil.MustClose(db) - err = db.Ping() - - return err + return db.Ping() } func (*Driver) QueryError(query string, err error) error { @@ -423,25 +346,12 @@ func (*Driver) QueryError(query string, err error) error { } func (drv *Driver) SelectMigrations(db *sql.DB, limit int) (map[string]bool, error) { - ctx := context.Background() - con, err := db.Conn(ctx) + config, err := drv.getConfig(db) if err != nil { return nil, err } - defer con.Close() - var dataset string - - err = con.Raw(func(driverConn any) error { - dataset = getDataset(driverConn) - return nil - }) - - if err != nil { - return nil, err - } - - query := fmt.Sprintf("SELECT version FROM %s.%s ORDER BY version DESC", dataset, drv.migrationsTableName) + query := fmt.Sprintf("SELECT version FROM %s.%s ORDER BY version DESC", config.dataSet, drv.migrationsTableName) if limit >= 0 { query = fmt.Sprintf("%s limit %d", query, limit) } @@ -449,7 +359,6 @@ func (drv *Driver) SelectMigrations(db *sql.DB, limit int) (map[string]bool, err if err != nil { return nil, err } - defer dbutil.MustClose(rows) migrations := map[string]bool{} @@ -610,6 +519,11 @@ func tableExists(client *bigquery.Client, datasetID, tableName string) (bool, er } func connectionString(u *url.URL) string { + return u.String() +} + +// nolint:unused +func connectionStringOld(u *url.URL) string { //if connecting to emulator with host:port format if u.Port() != "" { paths := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") @@ -649,27 +563,69 @@ func connectionString(u *url.URL) string { return newURL.String() } -func getClient(con any) *bigquery.Client { - value := reflect.ValueOf(con).Elem().FieldByName("client") - value = reflect.NewAt(value.Type(), unsafe.Pointer(value.UnsafeAddr())).Elem() - client := value.Interface().(*bigquery.Client) - return client +func (drv *Driver) getClient(db *sql.DB) (*bigquery.Client, error) { + conn, err := db.Conn(context.Background()) + if err != nil { + return nil, err + } + defer conn.Close() + + var client *bigquery.Client + + err = conn.Raw(func(driverConn any) error { + client = getClient(driverConn) + return nil + }) + if err != nil { + return nil, err + } + + return client, nil } -func getConfigValue(con any, field string) reflect.Value { - connValue := reflect.ValueOf(con).Elem() - configField := connValue.FieldByName("config") - return configField.FieldByName(field) +func getClient(driverConn any) *bigquery.Client { + value := reflect.ValueOf(driverConn).Elem().FieldByName("client") + value = reflect.NewAt(value.Type(), unsafe.Pointer(value.UnsafeAddr())) + return value.Elem().Interface().(*bigquery.Client) } -func getProjectID(con any) string { - return getConfigValue(con, "projectID").String() +// As the `bigQueryConfig` struct is unexported from `go-gorm/bigquery`, +// we need to maintain a copy here and access it through reflection. +// +// See: https://github.com/go-gorm/bigquery/blob/74582cba0726b82b8a59990fee4064e059e88c9b/driver/driver.go#L18-L27 +type bigQueryConfig struct { + projectID string + location string + dataSet string + scopes []string + endpoint string + disableAuth bool + credentialFile string + credentialJSON []byte } -func getDataset(con any) string { - return getConfigValue(con, "dataSet").String() +func (drv *Driver) getConfig(db *sql.DB) (*bigQueryConfig, error) { + conn, err := db.Conn(context.Background()) + if err != nil { + return nil, err + } + defer conn.Close() + + var config *bigQueryConfig + + err = conn.Raw(func(driverConn any) error { + config = getConfig(driverConn) + return nil + }) + if err != nil { + return nil, err + } + + return config, nil } -func getLocation(con any) string { - return getConfigValue(con, "location").String() +func getConfig(driverConn any) *bigQueryConfig { + value := reflect.ValueOf(driverConn).Elem().FieldByName("config") + value = reflect.NewAt(reflect.TypeOf(bigQueryConfig{}), unsafe.Pointer(value.UnsafeAddr())) + return value.Interface().(*bigQueryConfig) } diff --git a/pkg/driver/bigquery/bigquery_test.go b/pkg/driver/bigquery/bigquery_test.go index ecaed53a..461fe68a 100644 --- a/pkg/driver/bigquery/bigquery_test.go +++ b/pkg/driver/bigquery/bigquery_test.go @@ -1,15 +1,12 @@ package bigquery import ( - "context" "database/sql" + "fmt" "net/url" "os" - "reflect" "testing" - "unsafe" - "cloud.google.com/go/bigquery" "github.com/stretchr/testify/require" "github.com/amacneil/dbmate/v2/pkg/dbmate" @@ -24,6 +21,29 @@ func testBigQueryDriver(t *testing.T) *Driver { return drv.(*Driver) } +func testGoogleBigQueryDriver(t *testing.T) *Driver { + testURL, ok := os.LookupEnv("GOOGLE_BIGQUERY_TEST_URL") + if !ok { + t.Skip("skipping test, no GOOGLE_BIGQUERY_TEST_URL provided") + } + u := dbutil.MustParseURL(testURL) + + endpoint := u.Query().Get("endpoint") + if endpoint != "" { + endpointURL, err := url.Parse(endpoint) + require.NoError(t, err) + + if endpointURL.Hostname() != "bigquery.googleapis.com" { + t.Skipf("skipping test, GOOGLE_BIGQUERY_TEST_URL endpoint is %s and not bigquery.googleapis.com", endpointURL.Hostname()) + } + } + + drv, err := dbmate.New(u).Driver() + require.NoError(t, err) + + return drv.(*Driver) +} + func prepTestBigQueryDB(t *testing.T) *sql.DB { drv := testBigQueryDriver(t) @@ -42,6 +62,24 @@ func prepTestBigQueryDB(t *testing.T) *sql.DB { return db } +func prepTestGoogleBigQueryDB(t *testing.T) *sql.DB { + drv := testGoogleBigQueryDriver(t) + + // drop any existing database + err := drv.DropDatabase() + require.NoError(t, err) + + // create database + err = drv.CreateDatabase() + require.NoError(t, err) + + // connect database + db, err := drv.Open() + require.NoError(t, err) + + return db +} + func TestGetDriver(t *testing.T) { db := dbmate.New(dbutil.MustParseURL("bigquery://")) drvInterface, err := db.Driver() @@ -61,35 +99,23 @@ func TestGetClient(t *testing.T) { require.NoError(t, err) defer dbutil.MustClose(db) - conn, err := db.Conn(context.Background()) + client, err := drv.getClient(db) require.NoError(t, err) - defer conn.Close() - - err = conn.Raw(func(driverConn any) error { - value := reflect.ValueOf(driverConn).Elem().FieldByName("client") - value = reflect.NewAt(value.Type(), unsafe.Pointer(value.UnsafeAddr())).Elem() - - client := value.Interface().(*bigquery.Client) - require.Equal(t, "test", client.Project()) - - connValue := reflect.ValueOf(driverConn).Elem() - configField := connValue.FieldByName("config") - - projectField := configField.FieldByName("projectID") - require.True(t, projectField.IsValid()) - require.Equal(t, "test", projectField.String()) + require.Equal(t, "test", client.Project()) +} - datasetField := configField.FieldByName("dataSet") - require.True(t, datasetField.IsValid()) - require.Equal(t, "dbmate_test", datasetField.String()) +func TestGetConfig(t *testing.T) { + drv := testBigQueryDriver(t) - locationField := configField.FieldByName("location") - require.True(t, locationField.IsValid()) - require.Equal(t, "asia-southeast1", locationField.String()) + db, err := drv.Open() + require.NoError(t, err) + defer dbutil.MustClose(db) - return nil - }) + config, err := drv.getConfig(db) require.NoError(t, err) + require.Equal(t, "test", config.projectID) + require.Equal(t, "us-east5", config.location) + require.Equal(t, "dbmate_test", config.dataSet) } func TestConnectionString(t *testing.T) { @@ -99,14 +125,11 @@ func TestConnectionString(t *testing.T) { }{ {"bigquery://projectid/dataset", "bigquery://projectid/dataset"}, {"bigquery://projectid/location/dataset", "bigquery://projectid/location/dataset"}, - {"bigquery://projectid/location/dataset?disable_auth=false", "bigquery://projectid/location/dataset"}, - {"bigquery://projectid/location/dataset?disable_auth=true", "bigquery://projectid/location/dataset"}, - {"bigquery://projectid/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050", "bigquery://projectid/dataset"}, - {"bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true", "bigquery://projectid/location/dataset"}, - {"bigquery://bigquery:9050/test/dbmate_test?disable_auth=true", "bigquery://test/dbmate_test?disable_auth=true&endpoint=http%3A%2F%2Fbigquery%3A9050"}, - {"bigquery://0.0.0.0:9050/project/location/dataset?disable_auth=true", "bigquery://project/location/dataset?disable_auth=true&endpoint=http%3A%2F%2F0.0.0.0%3A9050"}, - {"bigquery://bigquery:9050/test/dbmate_test?disable_auth=false", "bigquery://test/dbmate_test?endpoint=http%3A%2F%2Fbigquery%3A9050"}, - {"bigquery://0.0.0.0:9050/project/location/dataset", "bigquery://project/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050"}, + {"bigquery://projectid/location/dataset?disable_auth=false", "bigquery://projectid/location/dataset?disable_auth=false"}, + {"bigquery://projectid/location/dataset?disable_auth=true", "bigquery://projectid/location/dataset?disable_auth=true"}, + {"bigquery://projectid/location/dataset?endpoint=https%3A%2F%2Fbigquery.googleapis.com", "bigquery://projectid/location/dataset?endpoint=https%3A%2F%2Fbigquery.googleapis.com"}, + {"bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050", "bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050"}, + {"bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true", "bigquery://projectid/location/dataset?endpoint=http%3A%2F%2F0.0.0.0%3A9050&disable_auth=true"}, } for _, c := range cases { @@ -322,3 +345,35 @@ func TestBigQueryMigrationsTableExists(t *testing.T) { require.NoError(t, err) require.Equal(t, true, exists) } + +func TestGoogleBigQueryDumpSchema(t *testing.T) { + t.Run("default migrations table", func(t *testing.T) { + drv := testGoogleBigQueryDriver(t) + + // prepare database + db := prepTestGoogleBigQueryDB(t) + defer dbutil.MustClose(db) + err := drv.CreateMigrationsTable(db) + require.NoError(t, err) + + // insert migration + err = drv.InsertMigration(db, "abc1") + require.NoError(t, err) + err = drv.InsertMigration(db, "abc2") + require.NoError(t, err) + + // DumpSchema should return schema + config, err := drv.getConfig(db) + require.NoError(t, err) + + schema, err := drv.DumpSchema(db) + require.NoError(t, err) + require.Contains(t, string(schema), fmt.Sprintf("CREATE TABLE `%s.%s.schema_migrations`", config.projectID, config.dataSet)) + require.Contains(t, string(schema), "\n--\n"+ + "-- Dbmate schema migrations\n"+ + "--\n\n"+ + "INSERT INTO schema_migrations (version) VALUES\n"+ + " ('abc1'),\n"+ + " ('abc2');\n") + }) +} From 29b0a208607aeb03d347c82364b2022744207b32 Mon Sep 17 00:00:00 2001 From: Dossy Shiobara Date: Wed, 20 Mar 2024 00:30:33 -0400 Subject: [PATCH 17/17] Fix linter issues. --- pkg/driver/bigquery/bigquery.go | 130 +------------------------------- 1 file changed, 2 insertions(+), 128 deletions(-) diff --git a/pkg/driver/bigquery/bigquery.go b/pkg/driver/bigquery/bigquery.go index 2306882d..c477baca 100644 --- a/pkg/driver/bigquery/bigquery.go +++ b/pkg/driver/bigquery/bigquery.go @@ -256,7 +256,6 @@ func (drv *Driver) DumpSchema(db *sql.DB) ([]byte, error) { } return append(schema, migrations...), nil - } func (drv *Driver) MigrationsTableExists(db *sql.DB) (bool, error) { @@ -378,133 +377,6 @@ func (drv *Driver) SelectMigrations(db *sql.DB, limit int) (map[string]bool, err return migrations, nil } -func generateDDL(db *sql.DB, projectID, datasetID, objectName, objectType string) (string, error) { - var ddl string - - // Query to retrieve object information - switch objectType { - case "BASE TABLE": - // Query to retrieve column information for tables - query := fmt.Sprintf(` - SELECT - column_name, - data_type, - is_nullable - FROM - `+"`%s.%s.INFORMATION_SCHEMA.COLUMNS`"+` - WHERE - table_name = '%s'`, projectID, datasetID, objectName) - - // Execute the query - rows, err := db.Query(query) - if err != nil { - return "", err - } - defer dbutil.MustClose(rows) - - // Check for any error that occurred during the query execution - if err := rows.Err(); err != nil { - return "", err - } - - // Generate DDL for tables - for rows.Next() { - var columnName, dataType, isNullable string - if err := rows.Scan(&columnName, &dataType, &isNullable); err != nil { - return "", err - } - if isNullable == "YES" { - ddl += fmt.Sprintf("\t%s %s,\n", columnName, dataType) - } else { - ddl += fmt.Sprintf("\t%s %s %s,\n", columnName, dataType, "NOT NULL") - } - } - if ddl != "" { - ddl = fmt.Sprintf("CREATE TABLE `%s.%s.%s` (\n%s);", projectID, datasetID, objectName, ddl) - } - case "VIEW": - // Query to retrieve view definition - query := fmt.Sprintf(` - SELECT - view_definition - FROM - `+"`%s.%s.INFORMATION_SCHEMA.VIEWS`"+` - WHERE - table_name = '%s'`, projectID, datasetID, objectName) - - // Execute the query - row := db.QueryRow(query) - if err := row.Scan(&ddl); err != nil { - return "", err - } - ddl = fmt.Sprintf("CREATE VIEW `%s.%s.%s` AS\n%s;", projectID, datasetID, objectName, ddl) - ddl = strings.ReplaceAll(ddl, "\n", "\n\t") - case "FUNCTION": - // Query to retrieve function definition - definitionQuery := fmt.Sprintf(` - SELECT - routine_definition - FROM - `+"`%s.%s.INFORMATION_SCHEMA.ROUTINES`"+` - WHERE - routine_name = '%s'`, projectID, datasetID, objectName) - - // Execute the query to fetch function definition - definitionRow := db.QueryRow(definitionQuery) - if err := definitionRow.Scan(&ddl); err != nil { - return "", err - } - - // Query to retrieve function parameters - paramQuery := fmt.Sprintf(` - SELECT - parameter_name, - data_type, - ordinal_position - FROM - `+"`%s.%s.INFORMATION_SCHEMA.PARAMETERS`"+` - WHERE - specific_name = '%s'`, projectID, datasetID, objectName) - - // Execute the query to fetch function parameters - paramRows, err := db.Query(paramQuery) - if err != nil { - return "", err - } - defer dbutil.MustClose(paramRows) - - // Check for any error that occurred during the query execution - if err := paramRows.Err(); err != nil { - return "", err - } - - // Construct function parameters list - var paramList []string - var returnType string - for paramRows.Next() { - var paramName sql.NullString - var dataType string - var ordinalPosition int - if err := paramRows.Scan(¶mName, &dataType, &ordinalPosition); err != nil { - return "", err - } - if ordinalPosition == 0 { - returnType = dataType - } else { - paramList = append(paramList, fmt.Sprintf("%s %s", paramName.String, dataType)) - } - } - params := strings.Join(paramList, ", ") - - // Construct the function DDL with parameters - ddl = fmt.Sprintf("CREATE FUNCTION `%s.%s.%s` (%s) RETURNS %s AS (\n\t%s\n);", projectID, datasetID, objectName, params, returnType, ddl) - default: - return "", fmt.Errorf("unsupported object type: %s", objectType) - } - - return ddl, nil -} - // Helper function to check whether a table exists or not in a dataset func tableExists(client *bigquery.Client, datasetID, tableName string) (bool, error) { table := client.Dataset(datasetID).Table(tableName) @@ -593,6 +465,8 @@ func getClient(driverConn any) *bigquery.Client { // we need to maintain a copy here and access it through reflection. // // See: https://github.com/go-gorm/bigquery/blob/74582cba0726b82b8a59990fee4064e059e88c9b/driver/driver.go#L18-L27 +// +// nolint:unused type bigQueryConfig struct { projectID string location string