[
  {
    "path": ".circleci/config.yml",
    "content": "version: 2\n\njobs:\n  test:\n    docker:\n      - image: golang:1.23-bookworm\n    environment:\n      GO111MODULE: \"on\"\n    working_directory: /go/src/handy-spanner\n    steps:\n      - checkout\n      - restore_cache:\n          key: go-mod-{{ checksum \"go.sum\" }}\n      - run:\n          name: Install dependencies\n          command: |\n            if [ ! -d 'vendor' ]; then\n              go mod download\n            fi\n      - run:\n          name: build\n          command: |\n            make build\n\n      - run:\n          name: run tests\n          command: |\n            make test\n\nworkflows:\n  version: 2\n  build-workflow:\n    jobs:\n      - test\n"
  },
  {
    "path": ".gitignore",
    "content": "/handy-spanner\n/vendor/\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM golang:1.18-alpine3.16 AS builder\n\nRUN set -eux \\\n\t&& apk --no-cache add \\\n\t\tg++ \\\n\t\tgcc \\\n\t\tgit \\\n\t\tmake \\\n\t\tmusl-dev\n\nCOPY . /go/src/handy-spanner\nWORKDIR /go/src/handy-spanner\n\nRUN make build\n\nFROM alpine:3.16.1\n\nCOPY --from=builder /go/src/handy-spanner/handy-spanner /usr/local/bin/handy-spanner\n\nRUN apk --no-cache add \\\n\tca-certificates tzdata\n\nUSER nobody\nEXPOSE 9999\nENTRYPOINT [\"/usr/local/bin/handy-spanner\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2019 Masahiro Sano\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "all: test build\n\nexport GO111MODULE=on\n\n.PHONY: build\nbuild:\n\tgo build -v --tags \"json1\" ./cmd/handy-spanner\n\n.PHONY: test\ntest:\n\tgo test -v --tags \"json1\" -race ./...\n\ndocker-build:\n\tdocker build . -t handy-spanner\n"
  },
  {
    "path": "README.md",
    "content": "# An emulator for Cloud Spanner\n\n![Status: Maintenance](https://img.shields.io/badge/status-maintenance-yellow.svg)\n\n## ⚠️ Maintenance Mode\n\n> **Note:**\n> Active development of `handy-spanner` has ended due to the release of the [official Google Cloud Spanner Emulator](https://cloud.google.com/spanner/docs/emulator).\n>\n> This project is currently in **maintenance mode**. No new features will be added. We will only perform essential maintenance, such as updating dependencies.\n\n---\n\n## Install\n\n```\ngo get github.com/gcpug/handy-spanner/cmd/handy-spanner\n```\n\nNOTE: If you want to use some features (e.g. array literal `[]` in DML), require \"json1\" build tag.\nThe Spanner emulator uses sqlite3 internally. You may need to build go-sqlite3 explicitly.\nIt also requires cgo to use sqlite3.\n\n```\ngo get -u github.com/mattn/go-sqlite3\ngo install github.com/mattn/go-sqlite3\n```\n\n## Usage\n\n### Run as an independent process\n\n```\n./handy-spanner\n```\n\nor\n\n```\ndocker run --rm -it -p 9999:9999 handy-spanner\n```\n\nIt runs a hand-spanner server as a process. It serves spanner gRPC server by port 9999 by default.\n\n#### Access to the server\n\nThe google-cloud-go, the official Spanner SDK, supports to access an emulator server.\nSet the address to an emulator server to environment variable `SPANNER_EMULATOR_HOST`, then google-cloud-go transparently use the server in the client.\n\nSo if you want to replace spanner server with the handy-spanner you run, just do:\n\n```\nexport SPANNER_EMULATOR_HOST=localhost:9999\n```\n\n#### Schema setup\n\nThe handy-spanner server has no databases nor tables by default. You need to create them by yourself. You can prepare them at startup.\n\nhandy-spanner accepts some command-line arguments to prepare schema.\n\n* `schema`: Path to a DDL file which handy-spanner creates at startup\n* `project`: The project the DDL is applied.\n* `instance`: The instance the DDL is applied.\n* `database`: The database the DDL is applied.\n\nIf you prepare schema at startup, these 4 arguments are required.\n\n#### Implicit creation of databases\n\nWithout schema setup, databases are created automatically by your database access.\n\nhandy-spanner creates a database when a session to the database is created. In detail, when `CreateSession` or `BatchCreateSessions` is called, the accessed database is created.\n\n#### Database operations\n\nYou can also operate databases by your applications. If you need additional databases or database alterations, you need to follow this section.\n\nAs Cloud Spanner supports, there are dedicated gRPC service for instance and database operations for Spanner.\n\n* Instance operations\n  * [Document](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.admin.instance.v1)\n  * [Protobuf](https://github.com/googleapis/googleapis/blob/master/google/spanner/admin/instance/v1/spanner_instance_admin.proto)\n* Database operations\n  * [Document](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.admin.database.v1)\n  * [Protobuf](https://github.com/googleapis/googleapis/blob/master/google/spanner/admin/database/v1/spanner_database_admin.proto)\n\nhandy-spanner also supports these gRPC services so that you can operate databases. The gRPC services are provided in the same address to the normal gRPC service. It means it is provided in `localhost:9999` by default.\n\nIt seems most clients for each language for these gRPC services provided Google Cloud Platform dont support `SPANNER_EMULATOR_HOST` to connect an emulator server, so you need to setup a client to connect an emulator server explicitly. For Go, you can refer [examples](./fake/example_test.go) to connect an emulator server.\n\n### Run as a buillt-in server in Go\n\nIf you use a handy-spanner server in tests in Go, it's easier to run it in a process.\nSee an [example](https://github.com/gcpug/handy-spanner/blob/master/fake/example_test.go) for the details.\n\nNote that the tests highly depend on handhy-spanner, which means you cannot switch the backend depending on the situation. If you want to test on both Cloud Spanner and handy-spanner, it's better to use a handy-spanner server as an independent process.\n\n#### Database operations\n\nhandy-spanner provides utility functions to operate databases. For the detail, please refer [examples](./fake/example_test.go).\n\n* `ParseAndApplyDDL`\n  * You can pass `io.Reader` as a schema file to apply DDL to a database.\n* `ApplyDDL`\n  * You can pass `ast.DDL` as an already parsed definition to apply DDL to a database.\n\nYou can also operate databases or instances as Cloud Spanner supports. Please also refer _Run as an independent process_ section.\n\n## Can and Cannot\n\n### Supported features\n\n* Read\n   * Keys and KeyRange as KeySet\n   * Secondary index\n   * STORING columns for secondary index\n   * Respect column orders for index\n* Query\n   * Select result set by column name and *\n   * Most operators in WHERE clause: IN, BETWEEN, IS NULL\n   * Conditions in WHERE clause: =, !=, >, <, AND, OR\n   * Order By keyword with ASC, DESC\n   * Group By and Having statement\n   * LIMIT OFFSET\n   * SELECT alias\n   * Query Parameters\n   * Literals\n   * JOINs: COMMMA, CROSS, INNER, LEFT/RIGHT/FULL OUTER\n      * FULL OUTER has some limitations\n   * Subquery\n   * SET operations: UNION, INTERSECT, EXCEPT\n   * UNNEST: IN UNNEST, FROM UNNEST\n   * Functions (partially)\n   * Arithmetic operations\n* Mutation\n   * All mutation types: Insert, Update, InsertOrUpdate, Replace, Delete\n   * Commit timestamp\n* Transaction\n   * Isolation level: SERIALIZABLE\n* DML\n   * Insert, Update, Delete\n   * Batch DML\n* DDL\n   * CreateTable, CreateIndex only\n   * Respect INTERLEAVE\n* Data Types\n   * Int, Float, String, Bool, Byte, Date, Timestamp, Array<Any>, Struct\n* [Information Schema](https://cloud.google.com/spanner/docs/information-schema)\n   * partially supported\n\n### Not supported features\n\n* Transaction\n   * Timestamp bound read\n* Query\n   * Strict type checking\n   * More functions\n   * Partionan Query\n   * EXCEPT ALL and INTERSECT ALL (because of sqlite)\n   * FULL OUTER JOIN\n     * Not support table alias. Use `*` for now\n     * Not support ON condition. Use USING condition for now\n   * Merging INT64 and FLOAT64 in SET operations\n   * Array operations\n* DDL\n   * Alter Table, Drop Table, Drop Index\n   * Database management\n   * Long running operations\n* Replace\n   * wrong behavior on conflict\n* [Change Stream Schema](https://cloud.google.com/spanner/docs/change-streams)\n   * only can parse in [Database operations](#Database-operations) with `schema` flag.\n\n## Implementation\n\n### Transaction simulation\n\nhandy-spanner uses sqlite3 in [Shared-Cache Mode](https://www.sqlite.org/sharedcache.html). There is a characteristic in the trasactions.\n\n* Only one transaction can hold write lock per database to write database tables.\n    * Other transactions still can hold read lock.\n* Write transaction holds write lock against database tables while writing the tables.\n    * Other read transactions cannot read the table while locked\n* Read transaction holds read lock against database tables while reading the tables.\n    * Other read transactions can read the table by holding read lock\n    * Write transaction cannot write the table while read-locked\n\nIf we simply use the transactions, dead lock should happen in read and write locks. To simulate spanner transactions correctly as possible, handy-spanner manages sqlite3 transactions inside.\n\n* Each spanner transaction starts own sqlite3 transaction.\n* Only one spanner transaction can hold write lock per database.\n* While a transaction holds write lock, other spanner transactions cannot newly get read or write lock.\n* When write transaction tries to write a table, it forces transactions that hold read lock to the table to release the lock.\n   * The transactions become \"aborted\"\n* The aborted transactions are expected to be retried by the client.\n\n![abort](img/abort1.png) ![abort](img/abort2.png)\n\n### DML\n\nBecause of transaction limitations, DML also has limitations.\n\nWhen a transaction(A) updates a table, other transactions cannot read/write the table until the transaction(A) commits. This limitation may become an inconsistency to the Cloud Spanner. Other limitations are same to mutations with commit.\n\n![block](img/read_block.png)\n\n## Feature Request and Bug Report\n\nFeature requests and any bug reports are welcome.\n\nThere are many things to implement features and make better compatibility to Cloud Spanner. Now I'm prioritizing features by on-demand from users and me. Please create an issue if you find lack of a feature to use handy-spanner or imcompatibility to Cloud Spanner.\n\n#### Feature request\n\nPlease describe what feature you want. It's the best to refer a Cloud Spanner's document for the feature. If the feature is partiality implemented or wrong behavior you expected, please also see a Bug Report section.\n\n#### Bug report\n\nPlease describe a reproducable conditions. If you describe more information about the bug, it makes more easir to fix the bug.\n\n* Schema\n* Initial Data\n* Query or any requests you run\n* Actual behavior\n* Expected behavior\n* Document in Cloud Spanner\n\n## Copyright\n\n* Author: Masahiro Sano ([@kazegusuri](https://github.com/kazegusuri))\n* Copyright: 2019 Masahiro Sano\n* License: Apache License, Version 2.0\n"
  },
  {
    "path": "cmd/handy-spanner/main.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/gcpug/handy-spanner/fake\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\nvar (\n\taddr     = flag.String(\"addr\", \"0.0.0.0:9999\", \"address to listen\")\n\tproject  = flag.String(\"project\", \"fake\", \"project to apply DDL at startup\")\n\tdatabase = flag.String(\"database\", \"fake\", \"database to apply DDL at startup\")\n\tinstance = flag.String(\"instance\", \"fake\", \"instance to apply DDL at startup\")\n\tschema   = flag.String(\"schema\", \"\", \"path to a DDL file\")\n)\n\nfunc main() {\n\tflag.CommandLine.SetOutput(os.Stdout)\n\tflag.Parse()\n\n\tif err := runMain(); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"%s\\n\", err)\n\t\tos.Exit(1)\n\n\t}\n}\n\nfunc runMain() error {\n\tif *addr == \"\" {\n\t\treturn fmt.Errorf(\"addr must be specified\")\n\t}\n\n\tvar file *os.File\n\tif *schema != \"\" {\n\t\tf, err := os.Open(*schema)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to open schema file: %v\", err)\n\t\t}\n\t\tfile = f\n\t\tdefer file.Close()\n\t}\n\n\t// root context notifies server shutdown by SIGINT or SIGTERM\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tlis, err := net.Listen(\"tcp\", *addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tserver, err := fake.New(lis)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to setup fake server: %v\", err)\n\t}\n\n\twg, ctx := errgroup.WithContext(ctx)\n\twg.Go(func() error { return server.Start() })\n\n\tif file != nil {\n\t\tdbName := fmt.Sprintf(\"projects/%s/instances/%s/databases/%s\", *project, *instance, *database)\n\t\tif err := server.ParseAndApplyDDL(ctx, dbName, file); err != nil {\n\t\t\tserver.Stop()\n\t\t\treturn fmt.Errorf(\"failed apply DDL: %v\", err)\n\t\t}\n\t}\n\n\tlog.Print(\"spanner server is ready\")\n\n\t// Waiting for SIGTERM or Interrupt signal\n\tsigCh := make(chan os.Signal, 1)\n\tsignal.Notify(sigCh, syscall.SIGTERM, os.Interrupt)\n\tselect {\n\tcase <-sigCh:\n\t\tlog.Print(\"received SIGTERM, exiting server\")\n\tcase <-ctx.Done():\n\t}\n\n\t// stop server\n\tserver.Stop()\n\n\treturn wg.Wait()\n}\n"
  },
  {
    "path": "fake/example_test.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage fake_test\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"strings\"\n\n\t\"cloud.google.com/go/spanner\"\n\tadmindatabasev1 \"cloud.google.com/go/spanner/admin/database/apiv1\"\n\tdatabasepb \"cloud.google.com/go/spanner/admin/database/apiv1/databasepb\"\n\t\"github.com/gcpug/handy-spanner/fake\"\n\t\"google.golang.org/api/option\"\n)\n\nfunc Example_spannerClient() {\n\tctx := context.Background()\n\tdbName := \"projects/fake/instances/fake/databases/fake\"\n\n\t// Run fake server\n\tsrv, conn, err := fake.Run()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer srv.Stop()\n\tdefer conn.Close()\n\n\t// Prepare spanner client\n\tclient, err := spanner.NewClient(ctx, dbName, option.WithGRPCConn(conn))\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to connect fake spanner server: %v\", err)\n\t}\n\n\t// Use the client\n\t_, _ = client.Apply(ctx, []*spanner.Mutation{\n\t\tspanner.Insert(\"Test\",\n\t\t\t[]string{\"ColA\", \"ColB\"},\n\t\t\t[]interface{}{\"foo\", 100},\n\t\t),\n\t})\n\n\t// output:\n}\n\nfunc Example_adminClient() {\n\tctx := context.Background()\n\tdbName := \"projects/fake/instances/fake/databases/fake\"\n\tstmts := []string{\n\t\t`CREATE TABLE Table1 ( Id STRING(MAX) NOT NULL ) PRIMARY KEY (Id)`,\n\t\t`CREATE TABLE Table2 ( Id TIMESTAMP NOT NULL ) PRIMARY KEY (Id)`,\n\t}\n\n\t// Run fake server\n\tsrv, conn, err := fake.Run()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer srv.Stop()\n\n\t// Prepare spanner client\n\tadminclient, err := admindatabasev1.NewDatabaseAdminClient(ctx, option.WithGRPCConn(conn))\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to connect fake spanner server: %v\", err)\n\t}\n\n\t// Use the client\n\t_, err = adminclient.UpdateDatabaseDdl(ctx, &databasepb.UpdateDatabaseDdlRequest{\n\t\tDatabase:   dbName,\n\t\tStatements: stmts,\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// output:\n}\n\nfunc Example_applyDDL() {\n\tctx := context.Background()\n\tdbName := \"projects/fake/instances/fake/databases/fake\"\n\tschema := `\nCREATE TABLE Table1 ( Id STRING(MAX) NOT NULL ) PRIMARY KEY (Id);\nCREATE TABLE Table2 ( Id TIMESTAMP NOT NULL ) PRIMARY KEY (Id);\n`\n\n\t// Run fake server\n\tsrv, _, err := fake.Run()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer srv.Stop()\n\n\terr = srv.ParseAndApplyDDL(ctx, dbName, strings.NewReader(schema))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// output:\n}\n"
  },
  {
    "path": "fake/integration_test.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage fake\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"cloud.google.com/go/civil\"\n\t\"cloud.google.com/go/spanner\"\n\tadmindatabasev1 \"cloud.google.com/go/spanner/admin/database/apiv1\"\n\tdatabasepb \"cloud.google.com/go/spanner/admin/database/apiv1/databasepb\"\n\tcmp \"github.com/google/go-cmp/cmp\"\n\t\"google.golang.org/api/option\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\ntype FullType struct {\n\tPKey            string              `spanner:\"PKey\" json:\"PKey\"`                       // PKey\n\tFTString        string              `spanner:\"FTString\" json:\"FTString\"`               // FTString\n\tFTStringNull    spanner.NullString  `spanner:\"FTStringNull\" json:\"FTStringNull\"`       // FTStringNull\n\tFTBool          bool                `spanner:\"FTBool\" json:\"FTBool\"`                   // FTBool\n\tFTBoolNull      spanner.NullBool    `spanner:\"FTBoolNull\" json:\"FTBoolNull\"`           // FTBoolNull\n\tFTBytes         []byte              `spanner:\"FTBytes\" json:\"FTBytes\"`                 // FTBytes\n\tFTBytesNull     []byte              `spanner:\"FTBytesNull\" json:\"FTBytesNull\"`         // FTBytesNull\n\tFTTimestamp     time.Time           `spanner:\"FTTimestamp\" json:\"FTTimestamp\"`         // FTTimestamp\n\tFTTimestampNull spanner.NullTime    `spanner:\"FTTimestampNull\" json:\"FTTimestampNull\"` // FTTimestampNull\n\tFTInt           int64               `spanner:\"FTInt\" json:\"FTInt\"`                     // FTInt\n\tFTIntNull       spanner.NullInt64   `spanner:\"FTIntNull\" json:\"FTIntNull\"`             // FTIntNull\n\tFTFloat         float64             `spanner:\"FTFloat\" json:\"FTFloat\"`                 // FTFloat\n\tFTFloatNull     spanner.NullFloat64 `spanner:\"FTFloatNull\" json:\"FTFloatNull\"`         // FTFloatNull\n\tFTDate          civil.Date          `spanner:\"FTDate\" json:\"FTDate\"`                   // FTDate\n\tFTDateNull      spanner.NullDate    `spanner:\"FTDateNull\" json:\"FTDateNull\"`           // FTDateNull\n}\n\nvar fullTypesKeys = []string{\n\t\"PKey\", \"FTString\", \"FTStringNull\",\n\t\"FTBool\", \"FTBoolNull\",\n\t\"FTBytes\", \"FTBytesNull\",\n\t\"FTTimestamp\", \"FTTimestampNull\",\n\t\"FTInt\", \"FTIntNull\",\n\t\"FTFloat\", \"FTFloatNull\",\n\t\"FTDate\", \"FTDateNull\",\n}\n\ntype Simple struct {\n\tID    int64  `spanner:\"Id\"`\n\tValue string `spanner:\"Value\"`\n}\n\nfunc prepareSimpleData(t *testing.T, ctx context.Context, client *spanner.Client) {\n\t_, err := client.Apply(ctx, []*spanner.Mutation{\n\t\tspanner.Insert(\"Simple\", []string{\"Id\", \"Value\"},\n\t\t\t[]interface{}{100, \"xxx0\"},\n\t\t),\n\t\tspanner.Insert(\"Simple\", []string{\"Id\", \"Value\"},\n\t\t\t[]interface{}{101, \"xxx1\"},\n\t\t),\n\t\tspanner.Insert(\"Simple\", []string{\"Id\", \"Value\"},\n\t\t\t[]interface{}{102, \"xxx2\"},\n\t\t),\n\t\tspanner.Insert(\"Simple\", []string{\"Id\", \"Value\"},\n\t\t\t[]interface{}{200, \"yyy\"},\n\t\t),\n\t\tspanner.Insert(\"Simple\", []string{\"Id\", \"Value\"},\n\t\t\t[]interface{}{300, \"zzz\"},\n\t\t),\n\t\tspanner.Insert(\"Simple\", []string{\"Id\", \"Value\"},\n\t\t\t[]interface{}{400, \"zzz\"},\n\t\t),\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"Applying mutations: %v\", err)\n\t}\n}\n\nfunc prepareFullTypeData(t *testing.T, ctx context.Context, client *spanner.Client, now time.Time) {\n\t_, err := client.Apply(ctx, []*spanner.Mutation{\n\t\tspanner.Insert(\"FullTypes\", fullTypesKeys,\n\t\t\t[]interface{}{\n\t\t\t\t\"pkey0\",\n\t\t\t\t\"xxx0\", spanner.NullString{},\n\t\t\t\ttrue, spanner.NullBool{},\n\t\t\t\t[]byte(\"xxx0\"), []byte(\"xxx0\"),\n\t\t\t\tnow, spanner.NullTime{},\n\t\t\t\t100, spanner.NullInt64{},\n\t\t\t\t0.123, spanner.NullFloat64{},\n\t\t\t\tcivil.DateOf(now), spanner.NullDate{},\n\t\t\t},\n\t\t),\n\t\tspanner.Insert(\"FullTypes\", fullTypesKeys,\n\t\t\t[]interface{}{\n\t\t\t\t\"pkey1\",\n\t\t\t\t\"xxx1\", spanner.NullString{},\n\t\t\t\ttrue, spanner.NullBool{},\n\t\t\t\t[]byte(\"xxx1\"), []byte(\"xxx1\"),\n\t\t\t\tnow.Add(time.Second), spanner.NullTime{},\n\t\t\t\t100, spanner.NullInt64{},\n\t\t\t\t0.123, spanner.NullFloat64{},\n\t\t\t\tcivil.DateOf(now), spanner.NullDate{},\n\t\t\t},\n\t\t),\n\t\tspanner.Insert(\"FullTypes\", fullTypesKeys,\n\t\t\t[]interface{}{\n\t\t\t\t\"pkey2\",\n\t\t\t\t\"xxx2\", spanner.NullString{},\n\t\t\t\ttrue, spanner.NullBool{},\n\t\t\t\t[]byte(\"xxx2\"), []byte(\"xxx2\"),\n\t\t\t\tnow.Add(2 * time.Second), spanner.NullTime{},\n\t\t\t\t100, spanner.NullInt64{},\n\t\t\t\t0.123, spanner.NullFloat64{},\n\t\t\t\tcivil.DateOf(now), spanner.NullDate{},\n\t\t\t},\n\t\t),\n\t\tspanner.Insert(\"FullTypes\", fullTypesKeys,\n\t\t\t[]interface{}{\n\t\t\t\t\"pkey3\",\n\t\t\t\t\"yyy3\", spanner.NullString{},\n\t\t\t\ttrue, spanner.NullBool{},\n\t\t\t\t[]byte(\"yyy3\"), []byte(\"yyy3\"),\n\t\t\t\tnow.Add(3 * time.Second), spanner.NullTime{},\n\t\t\t\t200, spanner.NullInt64{},\n\t\t\t\t0.123, spanner.NullFloat64{},\n\t\t\t\tcivil.DateOf(now), spanner.NullDate{},\n\t\t\t},\n\t\t),\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"Applying mutations: %v\", err)\n\t}\n}\n\nfunc TestIntegration_ReadWrite(t *testing.T) {\n\tctx := context.Background()\n\tdbName := \"projects/fake/instances/fake/databases/fake\"\n\n\tf, err := os.Open(\"./testdata/schema.sql\")\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\n\tsrv, conn, err := Run()\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tdefer srv.Stop()\n\n\tif err := srv.ParseAndApplyDDL(ctx, dbName, f); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tclient, err := spanner.NewClient(ctx, dbName, option.WithGRPCConn(conn))\n\tif err != nil {\n\t\tt.Fatalf(\"Connecting to in-memory fake: %v\", err)\n\t}\n\n\tnow := time.Now()\n\tdate := civil.DateOf(now)\n\n\ttable := []struct {\n\t\texpected FullType\n\t}{\n\t\t{\n\t\t\texpected: FullType{\n\t\t\t\tPKey:     \"pkey1\",\n\t\t\t\tFTString: \"xxx1\",\n\t\t\t\tFTStringNull: spanner.NullString{\n\t\t\t\t\tStringVal: \"xxx1\",\n\t\t\t\t\tValid:     true,\n\t\t\t\t},\n\t\t\t\tFTBool: true,\n\t\t\t\tFTBoolNull: spanner.NullBool{\n\t\t\t\t\tBool:  true,\n\t\t\t\t\tValid: true,\n\t\t\t\t},\n\t\t\t\tFTBytes:     []byte(\"xxx1\"),\n\t\t\t\tFTBytesNull: []byte(\"xxx2\"),\n\t\t\t\tFTTimestamp: now,\n\t\t\t\tFTTimestampNull: spanner.NullTime{\n\t\t\t\t\tTime:  now,\n\t\t\t\t\tValid: true,\n\t\t\t\t},\n\t\t\t\tFTInt: 101,\n\t\t\t\tFTIntNull: spanner.NullInt64{\n\t\t\t\t\tInt64: 101,\n\t\t\t\t\tValid: true,\n\t\t\t\t},\n\t\t\t\tFTFloat: 0.123,\n\t\t\t\tFTFloatNull: spanner.NullFloat64{\n\t\t\t\t\tFloat64: 0.123,\n\t\t\t\t\tValid:   true,\n\t\t\t\t},\n\t\t\t\tFTDate: date,\n\t\t\t\tFTDateNull: spanner.NullDate{\n\t\t\t\t\tDate:  date,\n\t\t\t\t\tValid: true,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\texpected: FullType{\n\t\t\t\tPKey:            \"pkey1\",\n\t\t\t\tFTString:        \"xxx1\",\n\t\t\t\tFTStringNull:    spanner.NullString{},\n\t\t\t\tFTBool:          true,\n\t\t\t\tFTBoolNull:      spanner.NullBool{},\n\t\t\t\tFTBytes:         []byte(\"xxx1\"),\n\t\t\t\tFTBytesNull:     []byte(\"xxx2\"),\n\t\t\t\tFTTimestamp:     now,\n\t\t\t\tFTTimestampNull: spanner.NullTime{},\n\t\t\t\tFTInt:           101,\n\t\t\t\tFTIntNull:       spanner.NullInt64{},\n\t\t\t\tFTFloat:         0.123,\n\t\t\t\tFTFloatNull:     spanner.NullFloat64{},\n\t\t\t\tFTDate:          date,\n\t\t\t\tFTDateNull:      spanner.NullDate{},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range table {\n\t\t_, err = client.Apply(ctx, []*spanner.Mutation{\n\t\t\tspanner.Insert(\"FullTypes\", fullTypesKeys,\n\t\t\t\t[]interface{}{\n\t\t\t\t\ttc.expected.PKey,\n\t\t\t\t\ttc.expected.FTString,\n\t\t\t\t\ttc.expected.FTStringNull,\n\t\t\t\t\ttc.expected.FTBool, tc.expected.FTBoolNull,\n\t\t\t\t\ttc.expected.FTBytes, tc.expected.FTBytesNull,\n\t\t\t\t\ttc.expected.FTTimestamp, tc.expected.FTTimestampNull,\n\t\t\t\t\ttc.expected.FTInt, tc.expected.FTIntNull,\n\t\t\t\t\ttc.expected.FTFloat, tc.expected.FTFloatNull,\n\t\t\t\t\ttc.expected.FTDate, tc.expected.FTDateNull,\n\t\t\t\t}),\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Applying mutations: %v\", err)\n\t\t}\n\n\t\tvar results []*FullType\n\t\trows := client.Single().Read(ctx, \"FullTypes\", spanner.AllKeys(), fullTypesKeys)\n\t\terr = rows.Do(func(row *spanner.Row) error {\n\t\t\tvar ft FullType\n\t\t\tif err := row.ToStruct(&ft); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tresults = append(results, &ft)\n\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Iterating over all row read: %v\", err)\n\t\t}\n\n\t\tif len(results) != 1 {\n\t\t\tt.Fatalf(\"rows should be 1 but got %v row\", len(results))\n\t\t}\n\n\t\tif diff := cmp.Diff(&tc.expected, results[0]); diff != \"\" {\n\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t}\n\n\t\t_, err = client.Apply(ctx, []*spanner.Mutation{\n\t\t\tspanner.Delete(\"FullTypes\", spanner.AllKeys()),\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"delete failed: %v\", err)\n\t\t}\n\t}\n}\n\nfunc TestIntegration_ReadWrite_AtomicCount(t *testing.T) {\n\tctx := context.Background()\n\tdbName := \"projects/fake/instances/fake/databases/fake\"\n\n\tf, err := os.Open(\"./testdata/schema.sql\")\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\n\tsrv, conn, err := Run()\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tdefer srv.Stop()\n\n\tif err := srv.ParseAndApplyDDL(ctx, dbName, f); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tclient, err := spanner.NewClient(ctx, dbName, option.WithGRPCConn(conn))\n\tif err != nil {\n\t\tt.Fatalf(\"Connecting to in-memory fake: %v\", err)\n\t}\n\n\tif _, err := client.Apply(ctx, []*spanner.Mutation{\n\t\tspanner.Insert(\"Simple\", []string{\"Id\", \"Value\"},\n\t\t\t[]interface{}{100, \"100\"},\n\t\t),\n\t}); err != nil {\n\t\tt.Fatalf(\"failed to apply: %v\", err)\n\t}\n\n\terrCh := make(chan error, 10)\n\twg := &sync.WaitGroup{}\n\tfor i := 0; i < 10; i++ {\n\t\twg.Add(1)\n\t\tgo func(me int) {\n\t\t\tdefer wg.Done()\n\n\t\t\tfor j := 0; j < 10; j++ {\n\t\t\t\t_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, tx *spanner.ReadWriteTransaction) error {\n\t\t\t\t\tvar num int\n\n\t\t\t\t\tit := tx.Read(ctx, \"Simple\", spanner.Key([]interface{}{100}), []string{\"Value\"})\n\t\t\t\t\terr := it.Do(func(r *spanner.Row) error {\n\t\t\t\t\t\tvar value string\n\t\t\t\t\t\tif err := r.Column(0, &value); err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tnum, _ = strconv.Atoi(value)\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\tnext := num + 1\n\n\t\t\t\t\treturn tx.BufferWrite([]*spanner.Mutation{\n\t\t\t\t\t\tspanner.Update(\"Simple\", []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\t[]interface{}{100, fmt.Sprint(next)},\n\t\t\t\t\t\t),\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\terrCh <- fmt.Errorf(\"Transaction failed: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}(i)\n\t}\n\n\twg.Wait()\n\n\tvar errs []error\nL:\n\tfor {\n\t\tselect {\n\t\tcase err := <-errCh:\n\t\t\terrs = append(errs, err)\n\t\tdefault:\n\t\t\tbreak L\n\t\t}\n\t}\n\n\tif len(errs) != 0 {\n\t\tfor _, err := range errs {\n\t\t\tt.Error(err)\n\t\t}\n\t\tt.FailNow()\n\t}\n\n\tvar num int\n\tit := client.Single().Read(ctx, \"Simple\", spanner.Key([]interface{}{100}), []string{\"Value\"})\n\terr = it.Do(func(r *spanner.Row) error {\n\t\tvar value string\n\t\tif err := r.Column(0, &value); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tnum, _ = strconv.Atoi(value)\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"Read failed: %v\", err)\n\t}\n\n\texpected := 200\n\tif num != expected {\n\t\tt.Fatalf(\"expect num to be %v, but got %v\", expected, num)\n\t}\n}\n\nfunc TestIntegration_Read_KeySet(t *testing.T) {\n\tctx := context.Background()\n\tdbName := \"projects/fake/instances/fake/databases/fake\"\n\n\tf, err := os.Open(\"./testdata/schema.sql\")\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\n\tsrv, conn, err := Run()\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tdefer srv.Stop()\n\n\tif err := srv.ParseAndApplyDDL(ctx, dbName, f); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tclient, err := spanner.NewClient(ctx, dbName, option.WithGRPCConn(conn))\n\tif err != nil {\n\t\tt.Fatalf(\"Connecting to in-memory fake: %v\", err)\n\t}\n\n\tprepareSimpleData(t, ctx, client)\n\n\ttable := map[string]struct {\n\t\tkeyset   spanner.KeySet\n\t\texpected []*Simple\n\t}{\n\t\t\"SingleKey\": {\n\t\t\tkeyset: spanner.Key([]interface{}{100}),\n\t\t\texpected: []*Simple{\n\t\t\t\t{ID: 100, Value: \"xxx0\"},\n\t\t\t},\n\t\t},\n\t\t\"MultipleKeys\": {\n\t\t\tkeyset: spanner.KeySets(\n\t\t\t\tspanner.Key([]interface{}{100}),\n\t\t\t\tspanner.Key([]interface{}{101}),\n\t\t\t\tspanner.Key([]interface{}{102}),\n\t\t\t),\n\t\t\texpected: []*Simple{\n\t\t\t\t{ID: 100, Value: \"xxx0\"},\n\t\t\t\t{ID: 101, Value: \"xxx1\"},\n\t\t\t\t{ID: 102, Value: \"xxx2\"},\n\t\t\t},\n\t\t},\n\t\t\"SingleRange_CloseClose\": {\n\t\t\tkeyset: spanner.KeyRange{\n\t\t\t\tStart: spanner.Key([]interface{}{100}),\n\t\t\t\tEnd:   spanner.Key([]interface{}{102}),\n\t\t\t\tKind:  spanner.ClosedClosed,\n\t\t\t},\n\t\t\texpected: []*Simple{\n\t\t\t\t{ID: 100, Value: \"xxx0\"},\n\t\t\t\t{ID: 101, Value: \"xxx1\"},\n\t\t\t\t{ID: 102, Value: \"xxx2\"},\n\t\t\t},\n\t\t},\n\t\t\"SingleRange_OpenClose\": {\n\t\t\tkeyset: spanner.KeyRange{\n\t\t\t\tStart: spanner.Key([]interface{}{100}),\n\t\t\t\tEnd:   spanner.Key([]interface{}{102}),\n\t\t\t\tKind:  spanner.OpenClosed,\n\t\t\t},\n\t\t\texpected: []*Simple{\n\t\t\t\t{ID: 101, Value: \"xxx1\"},\n\t\t\t\t{ID: 102, Value: \"xxx2\"},\n\t\t\t},\n\t\t},\n\t\t\"SingleRange_CloseOpen\": {\n\t\t\tkeyset: spanner.KeyRange{\n\t\t\t\tStart: spanner.Key([]interface{}{100}),\n\t\t\t\tEnd:   spanner.Key([]interface{}{102}),\n\t\t\t\tKind:  spanner.ClosedOpen,\n\t\t\t},\n\t\t\texpected: []*Simple{\n\t\t\t\t{ID: 100, Value: \"xxx0\"},\n\t\t\t\t{ID: 101, Value: \"xxx1\"},\n\t\t\t},\n\t\t},\n\t\t\"SingleRange_OpenOpen\": {\n\t\t\tkeyset: spanner.KeyRange{\n\t\t\t\tStart: spanner.Key([]interface{}{100}),\n\t\t\t\tEnd:   spanner.Key([]interface{}{102}),\n\t\t\t\tKind:  spanner.OpenOpen,\n\t\t\t},\n\t\t\texpected: []*Simple{\n\t\t\t\t{ID: 101, Value: \"xxx1\"},\n\t\t\t},\n\t\t},\n\t\t\"SingleMultiRange\": {\n\t\t\tkeyset: spanner.KeySets(\n\t\t\t\tspanner.KeyRange{\n\t\t\t\t\tStart: spanner.Key([]interface{}{100}),\n\t\t\t\t\tEnd:   spanner.Key([]interface{}{103}),\n\t\t\t\t\tKind:  spanner.ClosedClosed,\n\t\t\t\t},\n\t\t\t\tspanner.KeyRange{\n\t\t\t\t\tStart: spanner.Key([]interface{}{200}),\n\t\t\t\t\tEnd:   spanner.Key([]interface{}{300}),\n\t\t\t\t\tKind:  spanner.ClosedOpen,\n\t\t\t\t},\n\t\t\t),\n\t\t\texpected: []*Simple{\n\t\t\t\t{ID: 100, Value: \"xxx0\"},\n\t\t\t\t{ID: 101, Value: \"xxx1\"},\n\t\t\t\t{ID: 102, Value: \"xxx2\"},\n\t\t\t\t{ID: 200, Value: \"yyy\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tc := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar results []*Simple\n\t\t\trows := client.Single().Read(ctx, \"Simple\", tc.keyset, []string{\"Id\", \"Value\"})\n\t\t\terr = rows.Do(func(row *spanner.Row) error {\n\t\t\t\tvar s Simple\n\n\t\t\t\tif err := row.ToStruct(&s); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tresults = append(results, &s)\n\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Iterating over all row read: %v\", err)\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(tc.expected, results); diff != \"\" {\n\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIntegration_Read_KeySet2(t *testing.T) {\n\tctx := context.Background()\n\tdbName := \"projects/fake/instances/fake/databases/fake\"\n\n\tf, err := os.Open(\"./testdata/schema.sql\")\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\n\tsrv, conn, err := Run()\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tdefer srv.Stop()\n\n\tif err := srv.ParseAndApplyDDL(ctx, dbName, f); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tclient, err := spanner.NewClient(ctx, dbName, option.WithGRPCConn(conn))\n\tif err != nil {\n\t\tt.Fatalf(\"Connecting to in-memory fake: %v\", err)\n\t}\n\n\tnow := time.Now().UTC()\n\tprepareFullTypeData(t, ctx, client, now)\n\n\ttable := map[string]struct {\n\t\tindex    string\n\t\tkeyset   spanner.KeySet\n\t\texpected []*FullType\n\t}{\n\t\t\"TimestampAsc_AsPrefix\": {\n\t\t\tindex:  \"FullTypesByIntTimestamp\",\n\t\t\tkeyset: spanner.Key{100}.AsPrefix(),\n\t\t\texpected: []*FullType{\n\t\t\t\t{PKey: \"pkey0\", FTInt: 100, FTTimestamp: now},\n\t\t\t\t{PKey: \"pkey1\", FTInt: 100, FTTimestamp: now.Add(time.Second)},\n\t\t\t\t{PKey: \"pkey2\", FTInt: 100, FTTimestamp: now.Add(2 * time.Second)},\n\t\t\t},\n\t\t},\n\t\t\"TimestamAsc_Range\": {\n\t\t\tindex: \"FullTypesByIntTimestamp\",\n\t\t\tkeyset: &spanner.KeyRange{\n\t\t\t\tStart: spanner.Key{100, now},\n\t\t\t\tEnd:   spanner.Key{100},\n\t\t\t\tKind:  spanner.ClosedOpen,\n\t\t\t},\n\t\t\texpected: []*FullType{\n\t\t\t\t{PKey: \"pkey0\", FTInt: 100, FTTimestamp: now},\n\t\t\t\t{PKey: \"pkey1\", FTInt: 100, FTTimestamp: now.Add(time.Second)},\n\t\t\t\t{PKey: \"pkey2\", FTInt: 100, FTTimestamp: now.Add(2 * time.Second)},\n\t\t\t},\n\t\t},\n\t\t\"TimestamAsc_Range2\": {\n\t\t\tindex: \"FullTypesByIntTimestamp\",\n\t\t\tkeyset: &spanner.KeyRange{\n\t\t\t\tStart: spanner.Key{100, time.Time{}},\n\t\t\t\tEnd:   spanner.Key{100, now.Add(2 * time.Second)},\n\t\t\t\tKind:  spanner.ClosedOpen,\n\t\t\t},\n\t\t\texpected: []*FullType{\n\t\t\t\t{PKey: \"pkey0\", FTInt: 100, FTTimestamp: now},\n\t\t\t\t{PKey: \"pkey1\", FTInt: 100, FTTimestamp: now.Add(time.Second)},\n\t\t\t},\n\t\t},\n\t\t\"TimestampDesc_AsPrefix\": {\n\t\t\tindex:  \"FullTypesByIntTimestampReverse\",\n\t\t\tkeyset: spanner.Key{100}.AsPrefix(),\n\t\t\texpected: []*FullType{\n\t\t\t\t{PKey: \"pkey2\", FTInt: 100, FTTimestamp: now.Add(2 * time.Second)},\n\t\t\t\t{PKey: \"pkey1\", FTInt: 100, FTTimestamp: now.Add(time.Second)},\n\t\t\t\t{PKey: \"pkey0\", FTInt: 100, FTTimestamp: now},\n\t\t\t},\n\t\t},\n\t\t\"TimestampDesc_Range1\": {\n\t\t\tindex: \"FullTypesByIntTimestampReverse\",\n\t\t\tkeyset: &spanner.KeyRange{\n\t\t\t\tStart: spanner.Key{100, now.Add(time.Second)},\n\t\t\t\tEnd:   spanner.Key{100},\n\t\t\t\tKind:  spanner.ClosedOpen,\n\t\t\t},\n\t\t\texpected: []*FullType{\n\t\t\t\t{PKey: \"pkey1\", FTInt: 100, FTTimestamp: now.Add(time.Second)},\n\t\t\t\t{PKey: \"pkey0\", FTInt: 100, FTTimestamp: now},\n\t\t\t},\n\t\t},\n\t\t\"TimestampDesc_Range2\": {\n\t\t\tindex: \"FullTypesByIntTimestampReverse\",\n\t\t\tkeyset: &spanner.KeyRange{\n\t\t\t\tStart: spanner.Key{100, now.Add(time.Second)},\n\t\t\t\tEnd:   spanner.Key{100, time.Time{}},\n\t\t\t\tKind:  spanner.ClosedOpen,\n\t\t\t},\n\t\t\texpected: []*FullType{\n\t\t\t\t{PKey: \"pkey1\", FTInt: 100, FTTimestamp: now.Add(time.Second)},\n\t\t\t\t{PKey: \"pkey0\", FTInt: 100, FTTimestamp: now},\n\t\t\t},\n\t\t},\n\t\t\"TimestampDesc_Range3\": {\n\t\t\tindex: \"FullTypesByIntTimestampReverse\",\n\t\t\tkeyset: &spanner.KeyRange{\n\t\t\t\tStart: spanner.Key{100, now.Add(time.Second)},\n\t\t\t\tEnd:   spanner.Key{100, time.Unix(1, 0)},\n\t\t\t\tKind:  spanner.ClosedOpen,\n\t\t\t},\n\t\t\texpected: []*FullType{\n\t\t\t\t{PKey: \"pkey1\", FTInt: 100, FTTimestamp: now.Add(time.Second)},\n\t\t\t\t{PKey: \"pkey0\", FTInt: 100, FTTimestamp: now},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tc := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar results []*FullType\n\t\t\trows := client.Single().ReadUsingIndex(ctx, \"FullTypes\", tc.index, tc.keyset, []string{\"PKey\", \"FTInt\", \"FTTimestamp\"})\n\t\t\terr = rows.Do(func(row *spanner.Row) error {\n\t\t\t\tvar ft FullType\n\n\t\t\t\tif err := row.ToStruct(&ft); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tresults = append(results, &ft)\n\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Iterating over all row read: %v\", err)\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(tc.expected, results); diff != \"\" {\n\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIntegration_ReadWrite_Update(t *testing.T) {\n\tctx := context.Background()\n\tdbName := \"projects/fake/instances/fake/databases/fake\"\n\n\tf, err := os.Open(\"./testdata/schema.sql\")\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\n\tsrv, conn, err := Run()\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tdefer srv.Stop()\n\n\tif err := srv.ParseAndApplyDDL(ctx, dbName, f); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tclient, err := spanner.NewClient(ctx, dbName, option.WithGRPCConn(conn))\n\tif err != nil {\n\t\tt.Fatalf(\"Connecting to in-memory fake: %v\", err)\n\t}\n\n\tread := func(rows *spanner.RowIterator) ([]*Simple, error) {\n\t\tvar results []*Simple\n\t\terr = rows.Do(func(row *spanner.Row) error {\n\t\t\tvar s Simple\n\t\t\tif err := row.ToStruct(&s); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tresults = append(results, &s)\n\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"Iterating over all row read: %v\", err)\n\t\t}\n\t\treturn results, nil\n\t}\n\n\texpected1 := []*Simple{\n\t\t{ID: 100, Value: \"xxx\"},\n\t\t{ID: 101, Value: \"yyy\"},\n\t}\n\n\t_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, tx *spanner.ReadWriteTransaction) error {\n\t\tstmt := spanner.NewStatement(`INSERT INTO Simple (Id, Value) VALUES(@id1, @value1), (@id2, @value2)`)\n\t\tstmt.Params = map[string]interface{}{\n\t\t\t\"id1\":    100,\n\t\t\t\"value1\": \"xxx\",\n\t\t\t\"id2\":    101,\n\t\t\t\"value2\": \"yyy\",\n\t\t}\n\t\tif _, err := tx.Update(ctx, stmt); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\trows := tx.Read(ctx, \"Simple\", spanner.AllKeys(), []string{\"Id\", \"Value\"})\n\t\tresults, err := read(rows)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif diff := cmp.Diff(expected1, results); diff != \"\" {\n\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t}\n\n\t\tstmt2 := spanner.NewStatement(`UPDATE Simple SET Value = \"200\" WHERE Id = @id`)\n\t\tstmt2.Params = map[string]interface{}{\n\t\t\t\"id\": 100,\n\t\t}\n\t\tif _, err := tx.Update(ctx, stmt2); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tstmt3 := spanner.NewStatement(`DELETE FROM Simple WHERE Id = @id`)\n\t\tstmt3.Params = map[string]interface{}{\n\t\t\t\"id\": 101,\n\t\t}\n\t\tif _, err := tx.Update(ctx, stmt3); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"ReadWriteTransaction: %v\", err)\n\t}\n\n\texpected2 := []*Simple{\n\t\t{ID: 100, Value: \"200\"},\n\t}\n\n\tvar results []*Simple\n\trows := client.Single().Read(ctx, \"Simple\", spanner.AllKeys(), []string{\"Id\", \"Value\"})\n\tresults, err = read(rows)\n\tif err != nil {\n\t\tt.Fatalf(\"Read: %v\", err)\n\t}\n\n\tif diff := cmp.Diff(expected2, results); diff != \"\" {\n\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t}\n}\n\nfunc TestIntegration_ReadWrite_BatchUpdate(t *testing.T) {\n\tctx := context.Background()\n\tdbName := \"projects/fake/instances/fake/databases/fake\"\n\n\tf, err := os.Open(\"./testdata/schema.sql\")\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\n\tsrv, conn, err := Run()\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tdefer srv.Stop()\n\n\tif err := srv.ParseAndApplyDDL(ctx, dbName, f); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tclient, err := spanner.NewClient(ctx, dbName, option.WithGRPCConn(conn))\n\tif err != nil {\n\t\tt.Fatalf(\"Connecting to in-memory fake: %v\", err)\n\t}\n\n\tread := func(rows *spanner.RowIterator) ([]*Simple, error) {\n\t\tvar results []*Simple\n\t\terr = rows.Do(func(row *spanner.Row) error {\n\t\t\tvar s Simple\n\t\t\tif err := row.ToStruct(&s); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tresults = append(results, &s)\n\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"Iterating over all row read: %v\", err)\n\t\t}\n\t\treturn results, nil\n\t}\n\n\texpected1 := []*Simple{\n\t\t{ID: 100, Value: \"xxx\"},\n\t\t{ID: 101, Value: \"yyy2\"},\n\t}\n\n\t_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, tx *spanner.ReadWriteTransaction) error {\n\t\tstmt := spanner.NewStatement(`INSERT INTO Simple (Id, Value) VALUES(@id1, @value1), (@id2, @value2)`)\n\t\tstmt.Params = map[string]interface{}{\n\t\t\t\"id1\":    100,\n\t\t\t\"value1\": \"xxx\",\n\t\t\t\"id2\":    101,\n\t\t\t\"value2\": \"yyy\",\n\t\t}\n\t\tstmt2 := spanner.NewStatement(`UPDATE Simple SET Value = \"yyy2\" WHERE Id = 101`)\n\t\taffectedRows, err := tx.BatchUpdate(ctx, []spanner.Statement{stmt, stmt2})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif diff := cmp.Diff([]int64{2, 1}, affectedRows); diff != \"\" {\n\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t}\n\n\t\trows := tx.Read(ctx, \"Simple\", spanner.AllKeys(), []string{\"Id\", \"Value\"})\n\t\tresults, err := read(rows)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif diff := cmp.Diff(expected1, results); diff != \"\" {\n\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t}\n\n\t\tstmt3 := spanner.NewStatement(`UPDATE Simple SET Value = \"200\" WHERE Id = @id`)\n\t\tstmt3.Params = map[string]interface{}{\n\t\t\t\"id\": 100,\n\t\t}\n\t\tstmt4 := spanner.NewStatement(`UPDAT`)\n\t\taffectedRows, err = tx.BatchUpdate(ctx, []spanner.Statement{stmt3, stmt4})\n\t\tif err == nil {\n\t\t\tt.Errorf(\"unexpected success for batch update\")\n\t\t}\n\n\t\tst := status.Convert(err)\n\t\tr := regexp.MustCompile(`Statement 1: .* is not valid DML`)\n\t\tif !r.MatchString(st.Message()) {\n\t\t\tt.Errorf(\"unexpected error message: %v\", st.Message())\n\t\t}\n\t\tif st.Code() != codes.InvalidArgument {\n\t\t\tt.Errorf(\"expect error code %v but got %v\", codes.InvalidArgument, st.Code())\n\t\t}\n\t\tif diff := cmp.Diff([]int64{1}, affectedRows); diff != \"\" {\n\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t}\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"ReadWriteTransaction: %v\", err)\n\t}\n\n\texpected2 := []*Simple{\n\t\t{ID: 100, Value: \"200\"},\n\t\t{ID: 101, Value: \"yyy2\"},\n\t}\n\n\tvar results []*Simple\n\trows := client.Single().Read(ctx, \"Simple\", spanner.AllKeys(), []string{\"Id\", \"Value\"})\n\tresults, err = read(rows)\n\tif err != nil {\n\t\tt.Fatalf(\"Read: %v\", err)\n\t}\n\n\tif diff := cmp.Diff(expected2, results); diff != \"\" {\n\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t}\n}\n\nfunc TestIntegration_Query(t *testing.T) {\n\tctx := context.Background()\n\tdbName := \"projects/fake/instances/fake/databases/fake\"\n\n\tf, err := os.Open(\"./testdata/schema.sql\")\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\n\tsrv, conn, err := Run()\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tdefer srv.Stop()\n\n\tif err := srv.ParseAndApplyDDL(ctx, dbName, f); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tclient, err := spanner.NewClient(ctx, dbName, option.WithGRPCConn(conn))\n\tif err != nil {\n\t\tt.Fatalf(\"Connecting to in-memory fake: %v\", err)\n\t}\n\n\tprepareSimpleData(t, ctx, client)\n\n\ttable := []struct {\n\t\tsql      string\n\t\tparams   map[string]interface{}\n\t\texpected []*Simple\n\t}{\n\t\t{\n\t\t\tsql: \"SELECT * FROM Simple\",\n\t\t\texpected: []*Simple{\n\t\t\t\t{ID: 100, Value: \"xxx0\"},\n\t\t\t\t{ID: 101, Value: \"xxx1\"},\n\t\t\t\t{ID: 102, Value: \"xxx2\"},\n\t\t\t\t{ID: 200, Value: \"yyy\"},\n\t\t\t\t{ID: 300, Value: \"zzz\"},\n\t\t\t\t{ID: 400, Value: \"zzz\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsql: \"SELECT Id, Value FROM Simple\",\n\t\t\texpected: []*Simple{\n\t\t\t\t{ID: 100, Value: \"xxx0\"},\n\t\t\t\t{ID: 101, Value: \"xxx1\"},\n\t\t\t\t{ID: 102, Value: \"xxx2\"},\n\t\t\t\t{ID: 200, Value: \"yyy\"},\n\t\t\t\t{ID: 300, Value: \"zzz\"},\n\t\t\t\t{ID: 400, Value: \"zzz\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsql:    \"SELECT * FROM Simple WHERE Id = @id\",\n\t\t\tparams: map[string]interface{}{\"id\": 101},\n\t\t\texpected: []*Simple{\n\t\t\t\t{ID: 101, Value: \"xxx1\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsql: \"SELECT * FROM Simple WHERE Id IN UNNEST(@ids)\",\n\t\t\tparams: map[string]interface{}{\n\t\t\t\t\"ids\": []int64{101, 102},\n\t\t\t},\n\t\t\texpected: []*Simple{\n\t\t\t\t{ID: 101, Value: \"xxx1\"},\n\t\t\t\t{ID: 102, Value: \"xxx2\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsql: \"SELECT * FROM Simple WHERE Id IN UNNEST(@ids)\",\n\t\t\tparams: map[string]interface{}{\n\t\t\t\t\"ids\": []int64{},\n\t\t\t},\n\t\t\texpected: nil,\n\t\t},\n\t\t{\n\t\t\tsql: \"SELECT * FROM Simple WHERE Id IN UNNEST(@ids)\",\n\t\t\tparams: map[string]interface{}{\n\t\t\t\t\"ids\": []int64(nil),\n\t\t\t},\n\t\t\texpected: nil,\n\t\t},\n\t\t{\n\t\t\tsql: `SELECT a.* FROM Simple AS a JOIN Simple AS b ON a.Id = b.Id WHERE a.Id = @id`,\n\t\t\tparams: map[string]interface{}{\n\t\t\t\t\"id\": 200,\n\t\t\t},\n\t\t\texpected: []*Simple{\n\t\t\t\t{ID: 200, Value: \"yyy\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsql: \"SELECT DISTINCT Value FROM Simple\",\n\t\t\texpected: []*Simple{\n\t\t\t\t{ID: 0, Value: \"xxx0\"},\n\t\t\t\t{ID: 0, Value: \"xxx1\"},\n\t\t\t\t{ID: 0, Value: \"xxx2\"},\n\t\t\t\t{ID: 0, Value: \"yyy\"},\n\t\t\t\t{ID: 0, Value: \"zzz\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range table {\n\t\tctx, cancel := context.WithTimeout(ctx, time.Second)\n\t\tdefer cancel()\n\n\t\tstmt := spanner.NewStatement(tc.sql)\n\t\tstmt.Params = tc.params\n\t\tvar results []*Simple\n\t\trows := client.Single().Query(ctx, stmt)\n\t\terr = rows.Do(func(row *spanner.Row) error {\n\t\t\tvar s Simple\n\n\t\t\tif err := row.ToStruct(&s); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tresults = append(results, &s)\n\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Iterating over all row read: %v\", err)\n\t\t}\n\n\t\tif diff := cmp.Diff(tc.expected, results); diff != \"\" {\n\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t}\n\t}\n}\n\nfunc TestIntegration_Query2(t *testing.T) {\n\tctx := context.Background()\n\tdbName := \"projects/fake/instances/fake/databases/fake\"\n\n\tf, err := os.Open(\"./testdata/schema.sql\")\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\n\tsrv, conn, err := Run()\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tdefer srv.Stop()\n\n\tif err := srv.ParseAndApplyDDL(ctx, dbName, f); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tclient, err := spanner.NewClient(ctx, dbName, option.WithGRPCConn(conn))\n\tif err != nil {\n\t\tt.Fatalf(\"Connecting to in-memory fake: %v\", err)\n\t}\n\n\tnow := time.Now()\n\tprepareFullTypeData(t, ctx, client, now)\n\n\ttable := []struct {\n\t\tsql      string\n\t\tparams   map[string]interface{}\n\t\texpected []*FullType\n\t}{\n\t\t{\n\t\t\tsql: `SELECT PKey, FTInt, FTTimestamp FROM FullTypes WHERE FTInt = 100`,\n\t\t\texpected: []*FullType{\n\t\t\t\t{PKey: \"pkey0\", FTInt: 100, FTTimestamp: now},\n\t\t\t\t{PKey: \"pkey1\", FTInt: 100, FTTimestamp: now.Add(time.Second)},\n\t\t\t\t{PKey: \"pkey2\", FTInt: 100, FTTimestamp: now.Add(2 * time.Second)},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsql: `SELECT PKey, FTInt, FTTimestamp FROM FullTypes WHERE FTTimestamp BETWEEN @t1 AND @t2`,\n\t\t\tparams: map[string]interface{}{\n\t\t\t\t\"t1\": now,\n\t\t\t\t\"t2\": now.Add(2 * time.Second),\n\t\t\t},\n\t\t\texpected: []*FullType{\n\t\t\t\t{PKey: \"pkey0\", FTInt: 100, FTTimestamp: now},\n\t\t\t\t{PKey: \"pkey1\", FTInt: 100, FTTimestamp: now.Add(time.Second)},\n\t\t\t\t{PKey: \"pkey2\", FTInt: 100, FTTimestamp: now.Add(2 * time.Second)},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range table {\n\t\tctx, cancel := context.WithTimeout(ctx, time.Second)\n\t\tdefer cancel()\n\n\t\tstmt := spanner.NewStatement(tc.sql)\n\t\tstmt.Params = tc.params\n\t\tvar results []*FullType\n\t\trows := client.Single().Query(ctx, stmt)\n\t\terr = rows.Do(func(row *spanner.Row) error {\n\t\t\tvar ft FullType\n\n\t\t\tif err := row.ToStruct(&ft); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tresults = append(results, &ft)\n\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Iterating over all row read: %v\", err)\n\t\t}\n\n\t\tif diff := cmp.Diff(tc.expected, results); diff != \"\" {\n\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t}\n\t}\n}\n\nfunc TestIntegration_Query_Detail(t *testing.T) {\n\tctx := context.Background()\n\tdbName := \"projects/fake/instances/fake/databases/fake\"\n\n\tf, err := os.Open(\"./testdata/schema.sql\")\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\n\tsrv, conn, err := Run()\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tdefer srv.Stop()\n\n\tif err := srv.ParseAndApplyDDL(ctx, dbName, f); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tclient, err := spanner.NewClient(ctx, dbName, option.WithGRPCConn(conn))\n\tif err != nil {\n\t\tt.Fatalf(\"Connecting to in-memory fake: %v\", err)\n\t}\n\n\tprepareSimpleData(t, ctx, client)\n\n\ttable := []struct {\n\t\tsql      string\n\t\tparams   map[string]interface{}\n\t\tnames    []string\n\t\tcolumns  []interface{}\n\t\texpected [][]interface{}\n\t}{\n\t\t{\n\t\t\tsql:     \"SELECT * FROM Simple\",\n\t\t\tnames:   []string{\"Id\", \"Value\"},\n\t\t\tcolumns: []interface{}{int64(0), \"\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t{int64(100), string(\"xxx0\")},\n\t\t\t\t{int64(101), string(\"xxx1\")},\n\t\t\t\t{int64(102), string(\"xxx2\")},\n\t\t\t\t{int64(200), string(\"yyy\")},\n\t\t\t\t{int64(300), string(\"zzz\")},\n\t\t\t\t{int64(400), string(\"zzz\")},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsql:     \"SELECT COUNT(1) FROM Simple\",\n\t\t\tnames:   []string{\"\"}, // TODO\n\t\t\tcolumns: []interface{}{int64(0)},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t{int64(6)},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsql:     \"SELECT COUNT(1) AS count FROM Simple\",\n\t\t\tnames:   []string{\"count\"},\n\t\t\tcolumns: []interface{}{int64(0)},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t{int64(6)},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsql:   \"SELECT 10, -10, 010, 0x10, 0X10\",\n\t\t\tnames: []string{\"\", \"\", \"\", \"\", \"\"},\n\t\t\tcolumns: []interface{}{\n\t\t\t\tint64(0), int64(0), int64(0), int64(0), int64(0),\n\t\t\t},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t{int64(10), int64(-10), int64(8), int64(16), int64(16)},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsql:   \"SELECT 1.1, .1, 123.456e-67, .1E4, 58., 4e2\",\n\t\t\tnames: []string{\"\", \"\", \"\", \"\", \"\", \"\"},\n\t\t\tcolumns: []interface{}{\n\t\t\t\tfloat64(0), float64(0), float64(0), float64(0), float64(0), float64(0),\n\t\t\t},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t{float64(1.1), float64(.1), float64(123.456e-67), float64(.1e4), float64(58.), float64(4e2)},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range table {\n\t\tstmt := spanner.NewStatement(tc.sql)\n\t\tstmt.Params = tc.params\n\n\t\tvar result [][]interface{}\n\t\trows := client.Single().Query(ctx, stmt)\n\t\terr := rows.Do(func(row *spanner.Row) error {\n\t\t\tif diff := cmp.Diff(tc.names, row.ColumnNames()); diff != \"\" {\n\t\t\t\tt.Fatalf(\"(-got, +want)\\n%s\", diff)\n\t\t\t}\n\n\t\t\tvar data []interface{}\n\t\t\tfor i := range tc.columns {\n\t\t\t\ttyp := reflect.New(reflect.TypeOf(tc.columns[i]))\n\t\t\t\tif err := row.Column(i, typ.Interface()); err != nil {\n\t\t\t\t\tt.Fatalf(\"Column error: %v\", err)\n\t\t\t\t}\n\t\t\t\tdata = append(data, reflect.Indirect(typ).Interface())\n\t\t\t}\n\t\t\tresult = append(result, data)\n\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Iterating over all row read: %v\", err)\n\t\t}\n\n\t\tif diff := cmp.Diff(tc.expected, result); diff != \"\" {\n\t\t\tt.Fatalf(\"(-got, +want)\\n%s\", diff)\n\t\t}\n\n\t}\n}\n\nfunc TestIntegration_CreateDatabase(t *testing.T) {\n\tctx := context.Background()\n\tprojectID, instanceID, databaseID := \"fake\", \"fake\", \"fake\"\n\n\tsrv, conn, err := Run()\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tdefer srv.Stop()\n\n\tadminclient, err := admindatabasev1.NewDatabaseAdminClient(ctx, option.WithGRPCConn(conn))\n\tif err != nil {\n\t\tt.Fatalf(\"failed to connect fake spanner server: %v\", err)\n\t}\n\n\tf, err := os.Open(\"./testdata/schema.sql\")\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\n\tb, err := ioutil.ReadAll(f)\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tvar stmts []string\n\tfor _, s := range strings.Split(string(b), \";\") {\n\t\ts = strings.TrimSpace(s)\n\t\tif s == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tstmts = append(stmts, s)\n\t}\n\n\top, err := adminclient.CreateDatabase(ctx, &databasepb.CreateDatabaseRequest{\n\t\tParent:          fmt.Sprintf(\"projects/%s/instances/%s\", projectID, instanceID),\n\t\tCreateStatement: fmt.Sprintf(\"CREATE DATABASE `%s`\", databaseID),\n\t\tExtraStatements: stmts,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdb, err := op.Wait(ctx)\n\tif err != nil {\n\t\tt.Fatalf(\"Wait err: %v\", err)\n\t}\n\tif got, expect := db.GetName(), fmt.Sprintf(\"projects/%s/instances/%s/databases/%s\", projectID, instanceID, databaseID); got != expect {\n\t\tt.Fatalf(\"expected %s but got %s\", expect, db.GetName())\n\t}\n}\n\nfunc TestIntegration_UpdateDatbaseDdl(t *testing.T) {\n\tctx := context.Background()\n\tdbName := \"projects/fake/instances/fake/databases/fake\"\n\n\tsrv, conn, err := Run()\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tdefer srv.Stop()\n\n\tadminclient, err := admindatabasev1.NewDatabaseAdminClient(ctx, option.WithGRPCConn(conn))\n\tif err != nil {\n\t\tt.Fatalf(\"failed to connect fake spanner server: %v\", err)\n\t}\n\n\tf, err := os.Open(\"./testdata/schema.sql\")\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\n\tb, err := ioutil.ReadAll(f)\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tvar stmts []string\n\tfor _, s := range strings.Split(string(b), \";\") {\n\t\ts = strings.TrimSpace(s)\n\t\tif s == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tstmts = append(stmts, s)\n\t}\n\n\top, err := adminclient.UpdateDatabaseDdl(ctx, &databasepb.UpdateDatabaseDdlRequest{\n\t\tDatabase:   dbName,\n\t\tStatements: stmts,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := op.Wait(ctx); err != nil {\n\t\tt.Fatalf(\"Wait err: %v\", err)\n\t}\n}\n\nfunc TestIntegration_DropDatabase(t *testing.T) {\n\tctx := context.Background()\n\tprojectID, instanceID, databaseID := \"fake\", \"fake\", \"fake\"\n\n\tsrv, conn, err := Run()\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tdefer srv.Stop()\n\n\tadminclient, err := admindatabasev1.NewDatabaseAdminClient(ctx, option.WithGRPCConn(conn))\n\tif err != nil {\n\t\tt.Fatalf(\"failed to connect fake spanner server: %v\", err)\n\t}\n\n\t// prepare database\n\tf, err := os.Open(\"./testdata/schema.sql\")\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tb, err := ioutil.ReadAll(f)\n\tif err != nil {\n\t\tt.Fatalf(\"err %v\", err)\n\t}\n\tvar stmts []string\n\tfor _, s := range strings.Split(string(b), \";\") {\n\t\ts = strings.TrimSpace(s)\n\t\tif s == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tstmts = append(stmts, s)\n\t}\n\tif _, err := adminclient.CreateDatabase(ctx, &databasepb.CreateDatabaseRequest{\n\t\tParent:          fmt.Sprintf(\"projects/%s/instances/%s\", projectID, instanceID),\n\t\tCreateStatement: fmt.Sprintf(\"CREATE DATABASE `%s`\", databaseID),\n\t\tExtraStatements: stmts,\n\t}); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := adminclient.DropDatabase(ctx, &databasepb.DropDatabaseRequest{\n\t\tDatabase: fmt.Sprintf(\"projects/%s/instances/%s/databases/%s\", projectID, instanceID, databaseID),\n\t}); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "fake/server.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage fake\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"time\"\n\n\tlropb \"cloud.google.com/go/longrunning/autogen/longrunningpb\"\n\tadminv1pb \"cloud.google.com/go/spanner/admin/database/apiv1/databasepb\"\n\tspannerpb \"cloud.google.com/go/spanner/apiv1/spannerpb\"\n\t\"github.com/cloudspannerecosystem/memefish\"\n\t\"github.com/cloudspannerecosystem/memefish/ast\"\n\t\"github.com/cloudspannerecosystem/memefish/token\"\n\t\"github.com/gcpug/handy-spanner/server\"\n\t\"google.golang.org/grpc\"\n\tchannelzsvc \"google.golang.org/grpc/channelz/service\"\n\t\"google.golang.org/grpc/reflection\"\n)\n\ntype Server struct {\n\taddr       string\n\tlis        net.Listener\n\tgrpcServer *grpc.Server\n\tsrv        server.FakeSpannerServer\n}\n\nfunc Run() (*Server, *grpc.ClientConn, error) {\n\tl, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\tif l, err = net.Listen(\"tcp6\", \"[::1]:0\"); err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t}\n\n\tsrv, err := New(l)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tgo func() { _ = srv.Start() }()\n\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\tdefer cancel()\n\n\tconn, err := grpc.DialContext(ctx, srv.Addr(), grpc.WithInsecure(), grpc.WithBlock())\n\tif err != nil {\n\t\tsrv.Stop()\n\t\treturn nil, nil, err\n\t}\n\n\treturn srv, conn, nil\n}\n\n// New returns Server for fake spanner.\nfunc New(lis net.Listener) (*Server, error) {\n\ts := &Server{\n\t\taddr:       lis.Addr().String(),\n\t\tlis:        lis,\n\t\tgrpcServer: grpc.NewServer(),\n\t\tsrv:        server.NewFakeServer(),\n\t}\n\n\tadminv1pb.RegisterDatabaseAdminServer(s.grpcServer, s.srv)\n\tspannerpb.RegisterSpannerServer(s.grpcServer, s.srv)\n\tlropb.RegisterOperationsServer(s.grpcServer, s.srv)\n\n\treflection.Register(s.grpcServer)\n\tchannelzsvc.RegisterChannelzServiceToServer(s.grpcServer)\n\n\treturn s, nil\n}\n\nfunc (s *Server) Addr() string {\n\treturn s.addr\n}\n\nfunc (s *Server) Start() error {\n\treturn s.grpcServer.Serve(s.lis)\n}\n\nfunc (s *Server) Stop() {\n\ts.grpcServer.Stop()\n\t_ = s.lis.Close()\n}\n\nfunc (s *Server) ApplyDDL(ctx context.Context, databaseName string, ddl []ast.DDL) error {\n\tfor _, stmt := range ddl {\n\t\tif err := s.srv.ApplyDDL(ctx, databaseName, stmt); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Server) ParseAndApplyDDL(ctx context.Context, databaseName string, r io.Reader) error {\n\tb, err := ioutil.ReadAll(r)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tddl, err := (&memefish.Parser{\n\t\tLexer: &memefish.Lexer{\n\t\t\tFile: &token.File{FilePath: \"\", Buffer: string(b)},\n\t\t},\n\t}).ParseDDLs()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, stmt := range ddl {\n\t\tif err := s.srv.ApplyDDL(ctx, databaseName, stmt); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "fake/testdata/schema.sql",
    "content": "CREATE TABLE Simple (\n  Id INT64 NOT NULL,\n  Value STRING(MAX) NOT NULL,\n) PRIMARY KEY(Id);\n\nCREATE TABLE CompositePrimaryKeys (\n  Id INT64 NOT NULL,\n  PKey1 STRING(32) NOT NULL,\n  PKey2 INT64 NOT NULL,\n  Error INT64 NOT NULL,\n  X STRING(32) NOT NULL,\n  Y STRING(32) NOT NULL,\n  Z STRING(32) NOT NULL,\n) PRIMARY KEY(PKey1, PKey2);\n\nCREATE INDEX CompositePrimaryKeysByXY ON CompositePrimaryKeys(X, Y);\nCREATE INDEX CompositePrimaryKeysByError  ON CompositePrimaryKeys(Error);\n\nCREATE TABLE FullTypes (\n  PKey STRING(32) NOT NULL,\n  FTString STRING(32) NOT NULL,\n  FTStringNull STRING(32),\n  FTBool BOOL NOT NULL,\n  FTBoolNull BOOL,\n  FTBytes BYTES(32) NOT NULL,\n  FTBytesNull BYTES(32),\n  FTTimestamp TIMESTAMP NOT NULL,\n  FTTimestampNull TIMESTAMP,\n  FTInt INT64 NOT NULL,\n  FTIntNull INT64,\n  FTFloat FLOAT64 NOT NULL,\n  FTFloatNull FLOAT64,\n  FTDate DATE NOT NULL,\n  FTDateNull DATE,\n) PRIMARY KEY(PKey);\n\nCREATE UNIQUE INDEX FullTypesByFTString ON FullTypes(FTString);\nCREATE INDEX FullTypesByIntDate ON FullTypes(FTInt, FTDate);\nCREATE INDEX FullTypesByIntTimestamp ON FullTypes(FTInt, FTTimestamp);\nCREATE INDEX FullTypesByIntTimestampReverse ON FullTypes(FTInt, FTTimestamp DESC);\nCREATE INDEX FullTypesByTimestamp ON FullTypes(FTTimestamp);\n\nCREATE TABLE ArrayTypes (\n  Id INT64 NOT NULL,\n  ArrayString ARRAY<STRING(32)>,\n  ArrayBool ARRAY<BOOL>,\n  ArrayBytes ARRAY<BYTES(32)>,\n  ArrayTimestamp ARRAY<TIMESTAMP>,\n  ArrayInt ARRAY<INT64>,\n  ArrayFloat ARRAY<FLOAT64>,\n  ArrayDate ARRAY<DATE>,\n) PRIMARY KEY(Id);\n\nCREATE CHANGE STREAM EverythingStream\n  FOR ALL;\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/gcpug/handy-spanner\n\ngo 1.23.0\n\ntoolchain go1.23.4\n\nrequire (\n\tcloud.google.com/go v0.118.2\n\tcloud.google.com/go/iam v1.3.1\n\tcloud.google.com/go/longrunning v0.6.4\n\tcloud.google.com/go/spanner v1.75.0\n\tgithub.com/cloudspannerecosystem/memefish v0.6.1\n\tgithub.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13\n\tgithub.com/google/go-cmp v0.7.0\n\tgithub.com/google/uuid v1.6.0\n\tgithub.com/mattn/go-sqlite3 v1.14.14\n\tgolang.org/x/sync v0.11.0\n\tgoogle.golang.org/api v0.220.0\n\tgoogle.golang.org/genproto v0.0.0-20250207221924-e9438ea467c6\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6\n\tgoogle.golang.org/grpc v1.70.0\n\tgoogle.golang.org/protobuf v1.36.5\n)\n\nrequire (\n\tcel.dev/expr v0.19.2 // indirect\n\tcloud.google.com/go/auth v0.14.1 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect\n\tcloud.google.com/go/compute/metadata v0.6.0 // indirect\n\tcloud.google.com/go/monitoring v1.24.0 // indirect\n\tgithub.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect\n\tgithub.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect\n\tgithub.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/go-logr/logr v1.4.2 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect\n\tgithub.com/google/s2a-go v0.1.9 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.14.1 // indirect\n\tgithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect\n\tgo.opencensus.io v0.24.0 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.1.0 // indirect\n\tgo.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect\n\tgo.opentelemetry.io/otel v1.34.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.34.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.34.0 // indirect\n\tgo.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.34.0 // indirect\n\tgolang.org/x/crypto v0.33.0 // indirect\n\tgolang.org/x/net v0.35.0 // indirect\n\tgolang.org/x/oauth2 v0.26.0 // indirect\n\tgolang.org/x/sys v0.30.0 // indirect\n\tgolang.org/x/text v0.22.0 // indirect\n\tgolang.org/x/time v0.10.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20250207221924-e9438ea467c6 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cel.dev/expr v0.19.2 h1:V354PbqIXr9IQdwy4SYA4xa0HXaWq1BUPAGzugBY5V4=\ncel.dev/expr v0.19.2/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=\ncloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=\ncloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=\ncloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=\ncloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=\ncloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=\ncloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=\ncloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=\ncloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=\ncloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=\ncloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=\ncloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=\ncloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=\ncloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=\ncloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U=\ncloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=\ncloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=\ncloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU=\ncloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=\ncloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=\ncloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=\ncloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=\ncloud.google.com/go v0.118.2 h1:bKXO7RXMFDkniAAvvuMrAPtQ/VHrs9e7J5UT3yrGdTY=\ncloud.google.com/go v0.118.2/go.mod h1:CFO4UPEPi8oV21xoezZCrd3d81K4fFkDTEJu4R8K+9M=\ncloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=\ncloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=\ncloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=\ncloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o=\ncloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE=\ncloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM=\ncloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ=\ncloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw=\ncloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY=\ncloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg=\ncloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ=\ncloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k=\ncloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw=\ncloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI=\ncloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4=\ncloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M=\ncloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE=\ncloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE=\ncloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk=\ncloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc=\ncloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8=\ncloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc=\ncloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04=\ncloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8=\ncloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY=\ncloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM=\ncloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc=\ncloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU=\ncloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI=\ncloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8=\ncloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno=\ncloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak=\ncloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84=\ncloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A=\ncloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E=\ncloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4=\ncloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0=\ncloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY=\ncloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k=\ncloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ=\ncloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk=\ncloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0=\ncloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc=\ncloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI=\ncloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ=\ncloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI=\ncloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08=\ncloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o=\ncloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s=\ncloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0=\ncloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ=\ncloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY=\ncloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo=\ncloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg=\ncloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw=\ncloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY=\ncloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw=\ncloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI=\ncloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo=\ncloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0=\ncloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E=\ncloud.google.com/go/auth v0.14.1 h1:AwoJbzUdxA/whv1qj3TLKwh3XX5sikny2fc40wUl+h0=\ncloud.google.com/go/auth v0.14.1/go.mod h1:4JHUxlGXisL0AW8kXPtUF6ztuOksyfUQNFjfsOCXkPM=\ncloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=\ncloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=\ncloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=\ncloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8=\ncloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8=\ncloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM=\ncloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU=\ncloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc=\ncloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI=\ncloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss=\ncloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE=\ncloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE=\ncloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g=\ncloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4=\ncloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8=\ncloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM=\ncloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA=\ncloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw=\ncloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc=\ncloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E=\ncloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac=\ncloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q=\ncloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU=\ncloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY=\ncloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s=\ncloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI=\ncloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y=\ncloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss=\ncloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc=\ncloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM=\ncloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI=\ncloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0=\ncloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk=\ncloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q=\ncloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg=\ncloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590=\ncloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8=\ncloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk=\ncloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk=\ncloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE=\ncloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU=\ncloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U=\ncloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA=\ncloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M=\ncloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg=\ncloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s=\ncloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM=\ncloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk=\ncloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA=\ncloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY=\ncloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI=\ncloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4=\ncloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI=\ncloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y=\ncloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs=\ncloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=\ncloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=\ncloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=\ncloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=\ncloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=\ncloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=\ncloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=\ncloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=\ncloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=\ncloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE=\ncloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo=\ncloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA=\ncloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=\ncloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=\ncloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=\ncloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=\ncloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=\ncloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=\ncloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=\ncloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=\ncloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=\ncloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY=\ncloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck=\ncloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w=\ncloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg=\ncloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo=\ncloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4=\ncloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM=\ncloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA=\ncloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I=\ncloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4=\ncloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI=\ncloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s=\ncloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0=\ncloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs=\ncloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc=\ncloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE=\ncloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM=\ncloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M=\ncloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0=\ncloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8=\ncloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM=\ncloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ=\ncloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE=\ncloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo=\ncloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE=\ncloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0=\ncloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA=\ncloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE=\ncloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38=\ncloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w=\ncloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8=\ncloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I=\ncloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ=\ncloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM=\ncloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA=\ncloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A=\ncloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ=\ncloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs=\ncloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s=\ncloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI=\ncloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4=\ncloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo=\ncloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA=\ncloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM=\ncloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c=\ncloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo=\ncloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ=\ncloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g=\ncloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4=\ncloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs=\ncloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww=\ncloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c=\ncloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s=\ncloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI=\ncloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ=\ncloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4=\ncloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0=\ncloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8=\ncloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek=\ncloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0=\ncloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM=\ncloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4=\ncloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE=\ncloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM=\ncloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q=\ncloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4=\ncloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU=\ncloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU=\ncloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k=\ncloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4=\ncloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM=\ncloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs=\ncloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y=\ncloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg=\ncloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE=\ncloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk=\ncloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w=\ncloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc=\ncloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY=\ncloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU=\ncloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI=\ncloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8=\ncloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M=\ncloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc=\ncloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw=\ncloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw=\ncloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY=\ncloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w=\ncloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI=\ncloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs=\ncloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg=\ncloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE=\ncloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk=\ncloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg=\ncloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY=\ncloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08=\ncloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw=\ncloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA=\ncloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c=\ncloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM=\ncloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA=\ncloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w=\ncloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM=\ncloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0=\ncloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60=\ncloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo=\ncloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg=\ncloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o=\ncloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A=\ncloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw=\ncloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0=\ncloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0=\ncloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E=\ncloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw=\ncloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA=\ncloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI=\ncloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y=\ncloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc=\ncloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM=\ncloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o=\ncloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo=\ncloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=\ncloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=\ncloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc=\ncloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc=\ncloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg=\ncloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE=\ncloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY=\ncloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY=\ncloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0=\ncloud.google.com/go/iam v1.3.1 h1:KFf8SaT71yYq+sQtRISn90Gyhyf4X8RGgeAVC8XGf3E=\ncloud.google.com/go/iam v1.3.1/go.mod h1:3wMtuyT4NcbnYNPLMBzYRFiEfjKfJlLVLrisE7bwm34=\ncloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc=\ncloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A=\ncloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk=\ncloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo=\ncloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74=\ncloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM=\ncloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY=\ncloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4=\ncloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs=\ncloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g=\ncloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o=\ncloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE=\ncloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA=\ncloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg=\ncloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0=\ncloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg=\ncloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w=\ncloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24=\ncloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI=\ncloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=\ncloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=\ncloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE=\ncloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8=\ncloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY=\ncloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8=\ncloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08=\ncloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo=\ncloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw=\ncloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M=\ncloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE=\ncloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=\ncloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=\ncloud.google.com/go/longrunning v0.6.4 h1:3tyw9rO3E2XVXzSApn1gyEEnH2K9SynNQjMlBi3uHLg=\ncloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs=\ncloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE=\ncloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM=\ncloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA=\ncloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI=\ncloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw=\ncloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY=\ncloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4=\ncloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w=\ncloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I=\ncloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE=\ncloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM=\ncloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA=\ncloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY=\ncloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM=\ncloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY=\ncloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s=\ncloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8=\ncloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI=\ncloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo=\ncloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk=\ncloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4=\ncloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w=\ncloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw=\ncloud.google.com/go/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenToJxMM=\ncloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc=\ncloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA=\ncloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o=\ncloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM=\ncloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8=\ncloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E=\ncloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM=\ncloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8=\ncloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4=\ncloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY=\ncloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ=\ncloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU=\ncloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k=\ncloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU=\ncloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY=\ncloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34=\ncloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA=\ncloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0=\ncloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE=\ncloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ=\ncloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4=\ncloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs=\ncloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI=\ncloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA=\ncloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk=\ncloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ=\ncloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE=\ncloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc=\ncloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc=\ncloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs=\ncloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg=\ncloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo=\ncloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw=\ncloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw=\ncloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E=\ncloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU=\ncloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70=\ncloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo=\ncloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs=\ncloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0=\ncloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA=\ncloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk=\ncloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg=\ncloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE=\ncloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw=\ncloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc=\ncloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0=\ncloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI=\ncloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg=\ncloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI=\ncloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0=\ncloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8=\ncloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4=\ncloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg=\ncloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k=\ncloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM=\ncloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4=\ncloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o=\ncloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk=\ncloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo=\ncloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE=\ncloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U=\ncloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA=\ncloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c=\ncloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg=\ncloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4=\ncloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac=\ncloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg=\ncloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c=\ncloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs=\ncloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70=\ncloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ=\ncloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y=\ncloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A=\ncloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA=\ncloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM=\ncloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ=\ncloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA=\ncloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0=\ncloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots=\ncloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo=\ncloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI=\ncloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU=\ncloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg=\ncloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA=\ncloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4=\ncloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY=\ncloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc=\ncloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y=\ncloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14=\ncloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do=\ncloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo=\ncloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM=\ncloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg=\ncloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s=\ncloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI=\ncloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk=\ncloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44=\ncloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc=\ncloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc=\ncloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA=\ncloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4=\ncloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4=\ncloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU=\ncloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4=\ncloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0=\ncloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU=\ncloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q=\ncloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA=\ncloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8=\ncloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0=\ncloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU=\ncloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc=\ncloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk=\ncloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk=\ncloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0=\ncloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag=\ncloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU=\ncloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s=\ncloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA=\ncloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc=\ncloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk=\ncloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs=\ncloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg=\ncloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4=\ncloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U=\ncloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY=\ncloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s=\ncloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco=\ncloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo=\ncloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc=\ncloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4=\ncloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E=\ncloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU=\ncloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec=\ncloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA=\ncloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4=\ncloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw=\ncloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A=\ncloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos=\ncloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk=\ncloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M=\ncloud.google.com/go/spanner v1.75.0 h1:2zrltTJv/4P3pCgpYgde4Eb1vN8Cgy1fNy7pbTnOovg=\ncloud.google.com/go/spanner v1.75.0/go.mod h1:TLFZBvPQmx3We7sGh12eTk9lLsRLczzZaiweqfMpR80=\ncloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=\ncloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=\ncloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0=\ncloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco=\ncloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0=\ncloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ncloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=\ncloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=\ncloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc=\ncloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=\ncloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=\ncloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4=\ncloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w=\ncloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I=\ncloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4=\ncloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw=\ncloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw=\ncloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g=\ncloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM=\ncloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA=\ncloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c=\ncloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8=\ncloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4=\ncloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc=\ncloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ=\ncloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg=\ncloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM=\ncloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28=\ncloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y=\ncloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA=\ncloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk=\ncloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs=\ncloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg=\ncloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0=\ncloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos=\ncloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos=\ncloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk=\ncloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw=\ncloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg=\ncloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk=\ncloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ=\ncloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ=\ncloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU=\ncloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4=\ncloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M=\ncloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU=\ncloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU=\ncloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0=\ncloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo=\ncloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo=\ncloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY=\ncloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E=\ncloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY=\ncloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0=\ncloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE=\ncloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g=\ncloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc=\ncloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY=\ncloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208=\ncloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8=\ncloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY=\ncloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w=\ncloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8=\ncloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes=\ncloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE=\ncloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg=\ncloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc=\ncloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A=\ncloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg=\ncloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo=\ncloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ=\ncloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng=\ncloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=\ncloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=\ncloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M=\ncloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA=\ncloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=\ngit.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 h1:DBjmt6/otSdULyJdVg2BlG0qGZO5tKL4VzOs0jpvw5Q=\ngithub.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 h1:f2Qw/Ehhimh5uO1fayV0QIW7DShEQqhtUfhYc+cBPlw=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A=\ngithub.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=\ngithub.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A=\ngithub.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=\ngithub.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=\ngithub.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=\ngithub.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=\ngithub.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=\ngithub.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\ngithub.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=\ngithub.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI=\ngithub.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=\ngithub.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=\ngithub.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cloudspannerecosystem/memefish v0.6.1 h1:EJNZNq0E2vrYGBlu/xBs6jN7a5eq9ovF/wK+5Mo1iks=\ngithub.com/cloudspannerecosystem/memefish v0.6.1/go.mod h1:mVw0xBxy0yOgm990BuR0+nqP8J+yBAAf7N/2uL69rBU=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=\ngithub.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=\ngithub.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk=\ngithub.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=\ngithub.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=\ngithub.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=\ngithub.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=\ngithub.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=\ngithub.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34=\ngithub.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q=\ngithub.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=\ngithub.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=\ngithub.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=\ngithub.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=\ngithub.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=\ngithub.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=\ngithub.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=\ngithub.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=\ngithub.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=\ngithub.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=\ngithub.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=\ngithub.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=\ngithub.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=\ngithub.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=\ngithub.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=\ngithub.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=\ngithub.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=\ngithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=\ngithub.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=\ngithub.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=\ngithub.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=\ngithub.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=\ngithub.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=\ngithub.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=\ngithub.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=\ngithub.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=\ngithub.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=\ngithub.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=\ngithub.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=\ngithub.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=\ngithub.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=\ngithub.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=\ngithub.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=\ngithub.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo=\ngithub.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=\ngithub.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=\ngithub.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=\ngithub.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=\ngithub.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=\ngithub.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=\ngithub.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=\ngithub.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=\ngithub.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=\ngithub.com/k0kubun/pp/v3 v3.4.1 h1:1WdFZDRRqe8UsR61N/2RoOZ3ziTEqgTPVqKrHeb779Y=\ngithub.com/k0kubun/pp/v3 v3.4.1/go.mod h1:+SiNiqKnBfw1Nkj82Lh5bIeKQOAkPy6Xw9CAZUZ8npI=\ngithub.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=\ngithub.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=\ngithub.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=\ngithub.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=\ngithub.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=\ngithub.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw=\ngithub.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=\ngithub.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=\ngithub.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=\ngithub.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=\ngithub.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=\ngithub.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=\ngithub.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=\ngithub.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=\ngithub.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=\ngithub.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=\ngithub.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=\ngithub.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=\ngithub.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=\ngithub.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=\ngithub.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=\ngithub.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=\ngo.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=\ngo.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=\ngo.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=\ngo.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=\ngo.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=\ngo.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao=\ngo.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=\ngo.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=\ngo.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=\ngo.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=\ngo.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=\ngo.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=\ngo.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=\ngo.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=\ngo.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=\ngo.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=\ngo.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=\ngo.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=\ngo.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=\ngo.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=\ngolang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=\ngolang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=\ngolang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=\ngolang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=\ngolang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=\ngolang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=\ngolang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=\ngolang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=\ngolang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=\ngolang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=\ngolang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=\ngolang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=\ngolang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=\ngolang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=\ngolang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=\ngolang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=\ngolang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=\ngolang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=\ngolang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=\ngolang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=\ngolang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=\ngolang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=\ngolang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=\ngolang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=\ngolang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE=\ngolang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=\ngolang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=\ngolang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=\ngolang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=\ngolang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=\ngolang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=\ngolang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=\ngolang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=\ngolang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=\ngolang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=\ngolang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=\ngolang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngolang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngolang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=\ngonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=\ngonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0=\ngonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA=\ngonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=\ngonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=\ngonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY=\ngonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\ngoogle.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=\ngoogle.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=\ngoogle.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=\ngoogle.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=\ngoogle.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=\ngoogle.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=\ngoogle.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=\ngoogle.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=\ngoogle.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=\ngoogle.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=\ngoogle.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=\ngoogle.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=\ngoogle.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=\ngoogle.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=\ngoogle.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=\ngoogle.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=\ngoogle.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=\ngoogle.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=\ngoogle.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=\ngoogle.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=\ngoogle.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=\ngoogle.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=\ngoogle.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=\ngoogle.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=\ngoogle.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g=\ngoogle.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=\ngoogle.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=\ngoogle.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI=\ngoogle.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=\ngoogle.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=\ngoogle.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=\ngoogle.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08=\ngoogle.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70=\ngoogle.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo=\ngoogle.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=\ngoogle.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=\ngoogle.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=\ngoogle.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=\ngoogle.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=\ngoogle.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0=\ngoogle.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=\ngoogle.golang.org/api v0.220.0 h1:3oMI4gdBgB72WFVwE1nerDD8W3HUOS4kypK6rRLbGns=\ngoogle.golang.org/api v0.220.0/go.mod h1:26ZAlY6aN/8WgpCzjPNy18QpYaz7Zgg1h0qe1GkZEmY=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=\ngoogle.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=\ngoogle.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=\ngoogle.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=\ngoogle.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=\ngoogle.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=\ngoogle.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=\ngoogle.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=\ngoogle.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE=\ngoogle.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc=\ngoogle.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw=\ngoogle.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=\ngoogle.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=\ngoogle.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=\ngoogle.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=\ngoogle.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=\ngoogle.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=\ngoogle.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=\ngoogle.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo=\ngoogle.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE=\ngoogle.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA=\ngoogle.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=\ngoogle.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=\ngoogle.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA=\ngoogle.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=\ngoogle.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=\ngoogle.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=\ngoogle.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=\ngoogle.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=\ngoogle.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=\ngoogle.golang.org/genproto v0.0.0-20250207221924-e9438ea467c6 h1:SSk8oMbcHFbMwftDvX4PHbkqss3RkEZUF+k1h9d/sns=\ngoogle.golang.org/genproto v0.0.0-20250207221924-e9438ea467c6/go.mod h1:wkQ2Aj/xvshAUDtO/JHvu9y+AaN9cqs28QuSVSHtZSY=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250207221924-e9438ea467c6 h1:L9JNMl/plZH9wmzQUHleO/ZZDSN+9Gh41wPczNy+5Fk=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250207221924-e9438ea467c6/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 h1:2duwAxN2+k0xLNpjnHTXoMUgnv6VPSp5fiqTuwSxjmI=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=\ngoogle.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=\ngoogle.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=\ngoogle.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=\ngoogle.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=\ngoogle.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=\ngoogle.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=\ngoogle.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=\ngoogle.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=\ngoogle.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=\ngoogle.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=\ngoogle.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=\ngoogle.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=\ngoogle.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=\ngoogle.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=\ngoogle.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=\ngoogle.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=\ngoogle.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=\nlukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=\nlukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=\nmodernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=\nmodernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=\nmodernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=\nmodernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=\nmodernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=\nmodernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=\nmodernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=\nmodernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws=\nmodernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=\nmodernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=\nmodernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=\nmodernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=\nmodernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=\nmodernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU=\nmodernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=\nmodernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=\nmodernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=\nmodernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s=\nmodernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=\nmodernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=\nmodernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=\nmodernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=\nmodernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=\nmodernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=\nmodernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=\nmodernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=\nmodernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=\nmodernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=\nmodernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=\nmodernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=\nmodernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=\nmodernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\n"
  },
  {
    "path": "server/database.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"log\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/cloudspannerecosystem/memefish/ast\"\n\tuuidpkg \"github.com/google/uuid\"\n\tsqlite \"github.com/mattn/go-sqlite3\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tstructpb \"google.golang.org/protobuf/types/known/structpb\"\n)\n\ntype Database interface {\n\tApplyDDL(ctx context.Context, ddl ast.DDL) error\n\n\tRead(ctx context.Context, tx *transaction, tbl, index string, cols []string, keyset *KeySet, limit int64) (RowIterator, error)\n\tQuery(ctx context.Context, tx *transaction, query *ast.QueryStatement, params map[string]Value) (RowIterator, error)\n\tExecute(ctx context.Context, tx *transaction, dml ast.DML, params map[string]Value) (int64, error)\n\n\tInsert(ctx context.Context, tx *transaction, tbl string, cols []string, values []*structpb.ListValue) error\n\tUpdate(ctx context.Context, tx *transaction, tbl string, cols []string, values []*structpb.ListValue) error\n\tReplace(ctx context.Context, tx *transaction, tbl string, cols []string, values []*structpb.ListValue) error\n\tInsertOrUpdate(ctx context.Context, tx *transaction, tbl string, cols []string, values []*structpb.ListValue) error\n\tDelete(ctx context.Context, tx *transaction, table string, keyset *KeySet) error\n\n\tBeginTransaction(tx *transaction) error\n\tCommit(tx *transaction) error\n\tRollback(tx *transaction) error\n\n\tClose() error\n}\n\ntype barrier struct {\n\tlocked int32\n\tcondMu *sync.Mutex\n\tcond   *sync.Cond\n}\n\nfunc newBarrier() *barrier {\n\tmu := new(sync.Mutex)\n\tcond := sync.NewCond(mu)\n\treturn &barrier{\n\t\tlocked: 0,\n\t\tcondMu: mu,\n\t\tcond:   cond,\n\t}\n}\n\n// TryAcquire get a lock and blocks all other transactions enter critical section.\n// When fail to get a lock, returns false. Please use Wait to wait the lock is released and\n// try TryAcquire again.\nfunc (l *barrier) TryAcquire() bool {\n\tnow := atomic.LoadInt32(&l.locked)\n\tif now == 1 {\n\t\treturn false\n\t}\n\tok := atomic.CompareAndSwapInt32(&l.locked, now, 1)\n\treturn ok\n}\n\n// Release releases the lock and notifies to transactions that wait releasing th lock.\nfunc (l *barrier) Release() {\n\tatomic.StoreInt32(&l.locked, 0)\n\tl.cond.Broadcast()\n}\n\n// Wait waits until locked is released.\n// This does not ensure only one transaction enters critical section.\n// This makes sure one the lock is acquired by someone, other transactions are blocked until released.\nfunc (l *barrier) Wait() {\n\tnow := atomic.LoadInt32(&l.locked)\n\tif now == 0 {\n\t\treturn\n\t}\n\n\tl.condMu.Lock()\n\tl.cond.Wait()\n\tl.condMu.Unlock()\n}\n\nfunc (l *barrier) Released() bool {\n\tnow := atomic.LoadInt32(&l.locked)\n\treturn now == 0\n}\n\nvar _ Database = (*database)(nil)\n\ntype database struct {\n\tdb *sql.DB\n\n\tctx    context.Context\n\tcancel func()\n\n\t// schema level lock\n\tschemaMu sync.RWMutex\n\ttables   map[string]*Table\n\n\t// transactions\n\ttransactions   map[string]*transaction\n\ttransactionsMu sync.Mutex\n\ttablesInUse    map[string]*tableTransaction\n\ttablesInUseMu  sync.RWMutex\n\n\t// writeBarrier blocks other transactions try to write or commit\n\twriteBarrier       *barrier\n\twriteTransaction   *transaction\n\twriteTransactionMu sync.RWMutex\n}\n\ntype tableTransaction struct {\n\tuse                 sync.RWMutex\n\tlockHolder          *transaction\n\ttransactionsInUse   map[string]*transaction\n\ttransactionsInUseMu sync.Mutex\n}\n\nfunc (tt *tableTransaction) Dump() {\n\tif tt.lockHolder != nil {\n\t\tfmt.Printf(\"lock holder: %s status=%v\\n\", tt.lockHolder.Name(), tt.lockHolder.Status())\n\t} else {\n\t\tfmt.Printf(\"lock holder: <nil>\\n\")\n\t}\n\tfmt.Printf(\"transactions in use\\n\")\n\tfor _, tx := range tt.transactionsInUse {\n\t\tfmt.Printf(\" - %s status=%v\\n\", tx.Name(), tx.Status())\n\t}\n}\n\nfunc (tt *tableTransaction) Use(tx *transaction) {\n\tif IsDebug() {\n\t\tdefer DebugStartEnd(\"[%s] tableTransaction.Use\", tx.Name())()\n\t}\n\n\t// skip if the transction already holds the table lock\n\ttt.transactionsInUseMu.Lock()\n\tif tx.Equals(tt.lockHolder) {\n\t\ttt.transactionsInUseMu.Unlock()\n\t\treturn\n\t}\n\ttt.transactionsInUseMu.Unlock()\n\n\t// try to get read lock\n\ttt.use.RLock()\n\tdefer tt.use.RUnlock()\n\n\ttt.transactionsInUseMu.Lock()\n\tdefer tt.transactionsInUseMu.Unlock()\n\n\ttt.transactionsInUse[tx.Name()] = tx\n}\n\nfunc (tt *tableTransaction) Lock(tx *transaction) {\n\tif IsDebug() {\n\t\tdefer DebugStartEnd(\"[%s] tableTransaction.Lock\", tx.Name())()\n\t}\n\n\t// skip if the transction already holds the table lock\n\ttt.transactionsInUseMu.Lock()\n\tif tx.Equals(tt.lockHolder) {\n\t\ttt.transactionsInUseMu.Unlock()\n\t\treturn\n\t}\n\ttt.transactionsInUseMu.Unlock()\n\n\t// try to get write lock\n\ttt.use.Lock()\n\n\tvar uses []*transaction\n\tfunc() {\n\t\ttt.transactionsInUseMu.Lock()\n\t\tdefer tt.transactionsInUseMu.Unlock()\n\n\t\tif !tx.Available() {\n\t\t\tDebugf(\"[%s] transaction NOT AVAILABE in Lock: %v\\n\", tx.Name(), tx.Status())\n\t\t\tpanic(fmt.Sprintf(\"[%s] transaction NOT AVAILABE in Lock: %v\\n\", tx.Name(), tx.Status()))\n\t\t}\n\n\t\ttt.lockHolder = tx\n\n\t\tfor _, tx := range tt.transactionsInUse {\n\t\t\tuses = append(uses, tx)\n\t\t}\n\t}()\n\n\t// Abort all ransactions which hold read lock for the table\n\tfor _, tt := range uses {\n\t\tif !tx.Equals(tt) {\n\t\t\ttt.Done(TransactionAborted)\n\t\t}\n\t}\n}\n\nfunc (tt *tableTransaction) Release(tx *transaction) {\n\tif IsDebug() {\n\t\tdefer DebugStartEnd(\"[%s] tableTransaction.Release\", tx.Name())()\n\t}\n\n\ttt.transactionsInUseMu.Lock()\n\tdefer tt.transactionsInUseMu.Unlock()\n\n\tdelete(tt.transactionsInUse, tx.Name())\n\n\tif !tx.Equals(tt.lockHolder) {\n\t\treturn\n\t}\n\n\ttt.lockHolder = nil\n\ttt.use.Unlock()\n}\n\nfunc newTableTransaction() *tableTransaction {\n\treturn &tableTransaction{\n\t\ttransactionsInUse: make(map[string]*transaction),\n\t}\n}\n\nfunc newDatabase() *database {\n\tuuid := uuidpkg.New().String()\n\tdb, err := sql.Open(\"sqlite3_spanner\", fmt.Sprintf(\"file:%s.db?cache=shared&mode=memory&_foreign_keys=true\", uuid))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\n\tconn, err := db.Conn(ctx)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// keep at least 1 active connection to keep database\n\tgo func(conn *sql.Conn) {\n\t\tt := time.NewTicker(1 * time.Second)\n\t\tdefer t.Stop()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-t.C:\n\t\t\t}\n\n\t\t\tnewConn, err := db.Conn(ctx)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconn.Close()\n\t\t\tconn = newConn\n\t\t}\n\t}(conn)\n\n\td := &database{\n\t\ttables:      make(map[string]*Table),\n\t\ttablesInUse: make(map[string]*tableTransaction),\n\t\tdb:          db,\n\n\t\tctx:    ctx,\n\t\tcancel: cancel,\n\n\t\ttransactions: make(map[string]*transaction),\n\n\t\twriteBarrier: newBarrier(),\n\t}\n\n\tif err := d.prepareMetaTables(ctx); err != nil {\n\t\tlog.Fatalf(\"failed to prepare meta tables: %v\", err)\n\t}\n\n\treturn d\n}\n\nfunc (d *database) prepareMetaTables(ctx context.Context) error {\n\tfor _, table := range metaTables {\n\t\tif err := d.CreateTable(ctx, table); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create table for %s: %v\", table.Name.Idents[0].Name, err)\n\t\t}\n\t}\n\n\tunixmicro := int64(time.Now().UnixNano() / 1000)\n\tinitialData := []string{\n\t\tfmt.Sprintf(`INSERT INTO __INFORMATION_SCHEMA__SCHEMATA VALUES(\"\", \"\", %d)`, unixmicro),\n\t\t`INSERT INTO __INFORMATION_SCHEMA__SCHEMATA VALUES(\"\", \"INFORMATION_SCHEMA\", NULL)`,\n\t\t`INSERT INTO __INFORMATION_SCHEMA__SCHEMATA VALUES(\"\", \"SPANNER_SYS\", NULL)`,\n\t}\n\tfor _, query := range initialData {\n\t\tif _, err := d.db.ExecContext(ctx, query); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to prepare initial data: %v\", err)\n\t\t}\n\t}\n\n\tfor _, table := range metaTables {\n\t\tif err := d.registerInformationSchemaTables(ctx, table); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create table for %s: %v\", table.Name.Idents[0].Name, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// waitUntilReadable marks database is used for read.\n// If database is locked for write by other transaction, this function blocks until the lock is released.\nfunc (d *database) waitUntilReadable(ctx context.Context, tx *transaction) error {\n\tif IsDebug() {\n\t\tdefer DebugStartEnd(\"[%s] database.waitUntilReadable\", tx.Name())()\n\t}\n\n\t// Skip if the transaction already holds write lock\n\td.writeTransactionMu.RLock()\n\tcurTx := d.writeTransaction\n\td.writeTransactionMu.RUnlock()\n\tif tx.Equals(curTx) {\n\t\treturn nil\n\t}\n\n\tif d.writeBarrier.Released() {\n\t\treturn nil\n\t}\n\n\tch := make(chan struct{}, 0)\n\tgo func() {\n\t\td.writeBarrier.Wait()\n\t\tclose(ch)\n\t}()\n\n\tselect {\n\tcase <-ch:\n\tcase <-ctx.Done():\n\t\treturn status.FromContextError(ctx.Err()).Err()\n\t}\n\n\tif !tx.Available() {\n\t\treturn ErrNotAvailableTransaction\n\t}\n\n\treturn nil\n}\n\n// waitUntilWritable locks database for write.\n// This function does not ensure all other transactions don't have read lock.\n// Once locked, other transactions cannot newly get read or write lock.\n//\n// This function blocks until the lock is acquired. Break the block when the context\n// is done while waiting the lock.\nfunc (d *database) waitUntilWritable(ctx context.Context, tx *transaction) error {\n\tif IsDebug() {\n\t\tdefer DebugStartEnd(\"[%s] database.waitUntilWritable\", tx.Name())()\n\t}\n\n\t// Skip if the transaction already holds write lock\n\td.writeTransactionMu.RLock()\n\tcurTx := d.writeTransaction\n\td.writeTransactionMu.RUnlock()\n\tif tx.Equals(curTx) {\n\t\treturn nil\n\t}\n\n\tch := make(chan struct{}, 0)\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\n\t\t\tif !tx.Available() {\n\t\t\t\tclose(ch)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Try to get lock\n\t\t\tok := d.writeBarrier.TryAcquire()\n\t\t\tif ok {\n\t\t\t\t// save the holder of write lock\n\t\t\t\td.writeTransactionMu.Lock()\n\t\t\t\td.writeTransaction = tx\n\t\t\t\td.writeTransactionMu.Unlock()\n\t\t\t\tclose(ch)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Return if the lock is acrequired by own transaction\n\t\t\td.writeTransactionMu.RLock()\n\t\t\tcurTx := d.writeTransaction\n\t\t\td.writeTransactionMu.RUnlock()\n\t\t\tif curTx != nil && bytes.Equal(curTx.ID(), tx.ID()) {\n\t\t\t\tclose(ch)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Wait until the lock is released\n\t\t\td.writeBarrier.Wait()\n\t\t}\n\t}()\n\n\tselect {\n\tcase <-ch:\n\tcase <-ctx.Done():\n\t\treturn status.FromContextError(ctx.Err()).Err()\n\t}\n\n\tif !tx.Available() {\n\t\td.releaseWriteLock(tx)\n\t\treturn ErrNotAvailableTransaction\n\t}\n\n\treturn nil\n}\n\n// releasewriteLock releases write lock for database\n// This function can be called by any transactions but transactions don't hold write lock are ignored.\nfunc (d *database) releaseWriteLock(tx *transaction) {\n\tif IsDebug() {\n\t\tdefer DebugStartEnd(\"[%s] database.releaseWriteLock\", tx.Name())()\n\t}\n\n\td.writeTransactionMu.RLock()\n\tcurTx := d.writeTransaction\n\td.writeTransactionMu.RUnlock()\n\n\tif !curTx.Equals(tx) {\n\t\treturn\n\t}\n\n\td.writeTransactionMu.Lock()\n\tdefer d.writeTransactionMu.Unlock()\n\n\tcurTx = d.writeTransaction\n\tif curTx.Equals(tx) {\n\t\td.writeTransaction = nil\n\t\td.writeBarrier.Release()\n\t}\n}\n\n// useTable marks a table is used for read by the transaction.\n// If the table is locked by other transaction, this function blocks until the lock is released.\nfunc (d *database) useTable(tbl string, tx *transaction) (*Table, error) {\n\td.schemaMu.RLock()\n\tdefer d.schemaMu.RUnlock()\n\n\td.tablesInUseMu.Lock()\n\tdefer d.tablesInUseMu.Unlock()\n\n\ttable, ok := d.tables[tbl]\n\tif !ok {\n\t\treturn nil, newSpannerTableNotFoundError(tbl)\n\t}\n\n\tuse, ok := d.tablesInUse[tbl]\n\tif !ok {\n\t\tuse = newTableTransaction()\n\t\td.tablesInUse[tbl] = use\n\t}\n\n\tuse.Use(tx)\n\n\tif !tx.Available() {\n\t\treturn nil, ErrNotAvailableTransaction\n\t}\n\n\treturn table, nil\n}\n\n// useTableExclusive locks a table for write.\n// Other transactions cannot read or write the table and wait until the lock is released.\n// This function blocks until lock is acquired.\n//\n// When a transaction got the lock to a table, other transactions reading the table are\n// aborted immediately.\nfunc (d *database) useTableExclusive(tbl string, tx *transaction) (*Table, error) {\n\ttable, ok := d.tables[tbl]\n\tif !ok {\n\t\treturn nil, newSpannerTableNotFoundError(tbl)\n\t}\n\n\tvar tt *tableTransaction\n\tfunc() {\n\t\td.tablesInUseMu.Lock()\n\t\tdefer d.tablesInUseMu.Unlock()\n\n\t\tuse, ok := d.tablesInUse[tbl]\n\t\tif !ok {\n\t\t\tuse = newTableTransaction()\n\t\t\td.tablesInUse[tbl] = use\n\t\t}\n\n\t\ttt = use\n\t}()\n\n\ttt.Lock(tx)\n\n\treturn table, nil\n}\n\nfunc (d *database) ApplyDDL(ctx context.Context, ddl ast.DDL) error {\n\td.schemaMu.Lock()\n\tdefer d.schemaMu.Unlock()\n\n\tswitch val := ddl.(type) {\n\tcase *ast.CreateTable:\n\t\tif err := d.CreateTable(ctx, val); err != nil {\n\t\t\treturn status.Errorf(codes.Unknown, \"%v\", err)\n\t\t}\n\t\treturn nil\n\n\tcase *ast.CreateIndex:\n\t\tif err := d.CreateIndex(ctx, val); err != nil {\n\t\t\treturn status.Errorf(codes.Unknown, \"%v\", err)\n\t\t}\n\t\treturn nil\n\n\tcase *ast.DropTable:\n\t\treturn status.Errorf(codes.Unimplemented, \"Drop Table is not supported yet\")\n\n\tcase *ast.DropIndex:\n\t\treturn status.Errorf(codes.Unimplemented, \"Drop Index is not supported yet\")\n\n\tcase *ast.AlterTable:\n\t\treturn status.Errorf(codes.Unimplemented, \"Alter Table is not supported yet\")\n\n\tcase *ast.CreateChangeStream:\n\t\t// skip CreateChangeStream\n\t\treturn nil\n\tdefault:\n\t\treturn status.Errorf(codes.Unknown, \"unknown DDL statement: %v\", val)\n\t}\n}\n\nfunc (d *database) Read(ctx context.Context, tx *transaction, tbl, idx string, cols []string, keyset *KeySet, limit int64) (RowIterator, error) {\n\tif keyset == nil {\n\t\treturn nil, status.Errorf(codes.InvalidArgument, \"Invalid StreamingRead request\")\n\t}\n\tif tbl == \"\" {\n\t\treturn nil, status.Errorf(codes.InvalidArgument, \"Invalid StreamingRead request\")\n\t}\n\tif len(cols) == 0 {\n\t\treturn nil, status.Errorf(codes.InvalidArgument, \"Invalid StreamingRead request\")\n\t}\n\n\tif err := d.waitUntilReadable(ctx, tx); err != nil {\n\t\treturn nil, err\n\t}\n\n\ttable, err := d.useTable(tbl, tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tindex, ok := table.TableIndex(idx)\n\tif !ok {\n\t\treturn nil, newSpannerIndexnNotFoundError(tbl, idx)\n\t}\n\n\tcolumns, err := table.getColumnsByName(cols)\n\tif err != nil {\n\t\treturn nil, err // getColumnsByName returns error with status code\n\t}\n\n\t// Check the index table has the specified columns\n\tfor _, column := range columns {\n\t\tif !index.HasColumn(column.Name()) {\n\t\t\treturn nil, status.Errorf(codes.Unimplemented, \"Reading non-indexed columns using an index is not supported. Consider adding %s to the index using a STORING clause.\", column.Name())\n\t\t}\n\t}\n\n\tresultItems := make([]ResultItem, len(cols))\n\tfor i := range columns {\n\t\tresultItems[i] = createResultItemFromColumn(columns[i])\n\t}\n\n\tindexColumnsName := strings.Join(QuoteStringSlice(index.IndexColumnNames()), \", \")\n\tindexColumns := index.IndexColumns()\n\tindexColumnDirs := index.IndexColumnDirections()\n\tcolName := strings.Join(QuoteStringSlice(cols), \", \")\n\n\tvar args []interface{}\n\n\twhereClause, whereArgs, err := buildWhereClauseFromKeySet(keyset, indexColumnsName, indexColumns, indexColumnDirs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\targs = append(args, whereArgs...)\n\n\torderByItems := make([]string, len(indexColumns))\n\tfor i := range indexColumns {\n\t\torderByItems[i] = fmt.Sprintf(\"%s %s\", QuoteString(indexColumns[i].Name()), indexColumnDirs[i])\n\t}\n\torderByClause := strings.Join(orderByItems, \", \")\n\n\tquery := fmt.Sprintf(`SELECT %s FROM %s %s ORDER BY %s`, colName, QuoteString(table.Name), whereClause, orderByClause)\n\tif limit > 0 {\n\t\tquery += fmt.Sprintf(\" LIMIT %d\", limit)\n\t}\n\n\tvar sqlRows *sql.Rows\n\terr = tx.ReadTransaction(func(ctx context.Context, dbtx databaseReader) error {\n\t\tr, err := dbtx.QueryContext(ctx, query, args...)\n\t\tif err != nil {\n\t\t\treturn status.Errorf(codes.Unknown, \"query failed: %v, query: %v\", err, query)\n\t\t}\n\t\tsqlRows = r\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &rows{rows: sqlRows, resultItems: resultItems, transaction: tx}, nil\n}\n\nfunc (d *database) Query(ctx context.Context, tx *transaction, stmt *ast.QueryStatement, params map[string]Value) (RowIterator, error) {\n\tif err := d.waitUntilReadable(ctx, tx); err != nil {\n\t\treturn nil, err\n\t}\n\n\tquery, args, resultItems, err := BuildQuery(d, tx, stmt.Query, params, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar sqlRows *sql.Rows\n\terr = tx.ReadTransaction(func(ctx context.Context, dbtx databaseReader) error {\n\t\tr, err := dbtx.QueryContext(ctx, query, args...)\n\t\tif err != nil {\n\t\t\treturn status.Errorf(codes.Unknown, \"query failed: %v, query: %v\", err, query)\n\t\t}\n\t\tsqlRows = r\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &rows{rows: sqlRows, resultItems: resultItems, transaction: tx}, nil\n}\n\nfunc (d *database) Execute(ctx context.Context, tx *transaction, dml ast.DML, params map[string]Value) (int64, error) {\n\tif err := d.waitUntilWritable(ctx, tx); err != nil {\n\t\treturn 0, err\n\t}\n\n\tif !tx.Available() {\n\t\treturn 0, ErrNotAvailableTransaction\n\t}\n\n\tif tx.Status() == TransactionAborted {\n\t\treturn 0, status.Errorf(codes.Aborted, \"transaction aborted\")\n\t}\n\n\tquery, args, err := BuildDML(d, tx, dml, params)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tvar affectedRows int64\n\terr = tx.WriteTransaction(func(dbtx databaseWriter) error {\n\t\tr, err := dbtx.ExecContext(ctx, query, args...)\n\t\tif err != nil {\n\t\t\tif sqliteErr, ok := err.(sqlite.Error); ok {\n\t\t\t\t// This error should not be happend.\n\t\t\t\t// This error means a tx tries to write a table which another tx holds read-lock\n\t\t\t\t// to the table, or a tx tries to write a table which another tx holds global wite-lock\n\t\t\t\t//\n\t\t\t\t// It better to be panic but return Aborted to expect the client retries.\n\t\t\t\tif sqliteErr.Code == sqlite.ErrLocked {\n\t\t\t\t\treturn status.Errorf(codes.Aborted, \"transaction is aborted: database is locked\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn status.Errorf(codes.Unknown, \"failed to write into sqlite: %v\", err)\n\t\t}\n\n\t\taffectedRows, err = r.RowsAffected()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn affectedRows, nil\n}\n\nfunc (d *database) write(ctx context.Context, tx *transaction, tbl string, cols []string, values []*structpb.ListValue,\n\tnonNullCheck bool,\n\taffectedRowsCheck bool,\n\tbuildQueryFn func(*Table, []*Column) string,\n\tbuildArgsFn func(*Table, []*Column, []interface{}) []interface{},\n) error {\n\tif IsDebug() {\n\t\tdefer DebugStartEnd(\"[%s] database.write\", tx.Name())()\n\t}\n\n\tif err := d.waitUntilWritable(ctx, tx); err != nil {\n\t\treturn err\n\t}\n\n\tif !tx.Available() {\n\t\treturn ErrNotAvailableTransaction\n\t}\n\n\tif tx.Status() == TransactionAborted {\n\t\treturn status.Errorf(codes.Aborted, \"transaction aborted\")\n\t}\n\n\ttable, err := d.useTableExclusive(tbl, tx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif IsDebug() {\n\t\tdefer DebugStartEnd(\"[%s] database.write after write lock\", tx.Name())()\n\t}\n\n\tprimaryKey := table.primaryKey\n\n\t// Check columns are defined in the table\n\tcolumns, err := table.getColumnsByName(cols)\n\tif err != nil {\n\t\treturn err // getColumnsByName returns error with status code\n\t}\n\n\t// Ensure multiple values are not specified\n\tusedColumns := make(map[string]struct{}, len(cols))\n\tfor _, c := range columns {\n\t\tn := c.Name()\n\t\tif _, ok := usedColumns[n]; ok {\n\t\t\treturn status.Errorf(codes.InvalidArgument, \"Multiple values for column %s\", n)\n\t\t}\n\t\tusedColumns[n] = struct{}{}\n\t}\n\n\t// Check all primary keys are specified\n\tfor _, colName := range primaryKey.IndexColumnNames() {\n\t\tif _, ok := usedColumns[colName]; !ok {\n\t\t\treturn status.Errorf(codes.FailedPrecondition, \"%s must not be NULL in table %s.\", colName, tbl)\n\t\t}\n\t}\n\n\t// Check not nullable columns are specified for Insert/Replace\n\tif nonNullCheck {\n\t\tif exist, nonNullables := table.NonNullableAndNonGeneratedColumnsExist(cols); exist {\n\t\t\tcolumns := strings.Join(nonNullables, \", \")\n\t\t\treturn status.Errorf(codes.FailedPrecondition,\n\t\t\t\t\"A new row in table %s does not specify a non-null value for these NOT NULL columns: %s\",\n\t\t\t\ttbl, columns,\n\t\t\t)\n\t\t}\n\t}\n\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\n\tquery := buildQueryFn(table, columns)\n\tif query == \"\" {\n\t\treturn nil\n\t}\n\n\terr = tx.WriteTransaction(func(dbtx databaseWriter) error {\n\t\tfor _, vs := range values {\n\t\t\tif len(vs.Values) != len(cols) {\n\t\t\t\treturn status.Error(codes.InvalidArgument, \"Mutation has mismatched number of columns and values.\")\n\t\t\t}\n\n\t\t\tdata := make([]interface{}, 0, len(cols))\n\t\t\tfor i, v := range vs.Values {\n\t\t\t\tcol := columns[i]\n\n\t\t\t\tvv, err := spannerValue2DatabaseValue(v, *col)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t\t\t\t}\n\n\t\t\t\tif !col.nullable && vv == nil {\n\t\t\t\t\treturn status.Errorf(codes.FailedPrecondition,\n\t\t\t\t\t\t\"%s must not be NULL in table %s.\",\n\t\t\t\t\t\tcol.Name(), tbl,\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tdata = append(data, vv)\n\t\t\t}\n\n\t\t\targs := buildArgsFn(table, columns, data)\n\n\t\t\tr, err := dbtx.ExecContext(ctx, query, args...)\n\t\t\tif err != nil {\n\t\t\t\tif sqliteErr, ok := err.(sqlite.Error); ok {\n\t\t\t\t\tmsg := sqliteErr.Error()\n\t\t\t\t\tswitch sqliteErr.ExtendedCode {\n\t\t\t\t\tcase sqlite.ErrConstraintPrimaryKey:\n\t\t\t\t\t\treturn status.Errorf(codes.AlreadyExists, \"Row %v in table %s already exists\", data, tbl)\n\t\t\t\t\tcase sqlite.ErrConstraintUnique:\n\t\t\t\t\t\tif n := strings.Index(msg, \": \"); n > 0 {\n\t\t\t\t\t\t\tmsg = msg[n+2:]\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn status.Errorf(codes.AlreadyExists,\n\t\t\t\t\t\t\t\"Unique index violation at index key [%v]. It conflicts with row %v in table %s\",\n\t\t\t\t\t\t\tmsg, data, tbl,\n\t\t\t\t\t\t)\n\t\t\t\t\tcase sqlite.ErrConstraintCheck:\n\t\t\t\t\t\treturn status.Errorf(codes.FailedPrecondition, \"%s\", sqliteErr.Error())\n\t\t\t\t\t}\n\n\t\t\t\t\t// This error should not be happend.\n\t\t\t\t\t// This error means a tx tries to write a table which another tx holds read-lock\n\t\t\t\t\t// to the table, or a tx tries to write a table which another tx holds global wite-lock\n\t\t\t\t\t//\n\t\t\t\t\t// It better to be panic but return Aborted to expect the client retries.\n\t\t\t\t\tif sqliteErr.Code == sqlite.ErrLocked {\n\t\t\t\t\t\treturn status.Errorf(codes.Aborted, \"transaction is aborted: database is locked\")\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn status.Errorf(codes.Unknown, \"failed to write into sqlite: %v\", err)\n\t\t\t}\n\n\t\t\tif affectedRowsCheck {\n\t\t\t\t// Check rows are updated\n\t\t\t\t// When the row does not exist, sqlite returns success.\n\t\t\t\t// But spanner should return NotFound\n\t\t\t\tn, err := r.RowsAffected()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn status.Errorf(codes.Unknown, \"failed to get RowsAffected: %v\", err)\n\t\t\t\t}\n\t\t\t\tif n == 0 {\n\t\t\t\t\t// no details in real spanner\n\t\t\t\t\treturn status.Errorf(codes.NotFound, \"Row %v in table %s is missing. Row cannot be updated.\", data, tbl)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (d *database) Insert(ctx context.Context, tx *transaction, tbl string, cols []string, values []*structpb.ListValue) error {\n\tbuildQueryFn := func(table *Table, columns []*Column) string {\n\t\tcolumnName := strings.Join(QuoteStringSlice(cols), \", \")\n\t\tplaceholder := \"?\"\n\t\tif len(cols) > 1 {\n\t\t\tplaceholder += strings.Repeat(\", ?\", len(cols)-1)\n\t\t}\n\t\treturn fmt.Sprintf(`INSERT INTO %s (%s) VALUES (%s)`, QuoteString(tbl), columnName, placeholder)\n\t}\n\n\tbuildArgsFn := func(table *Table, columns []*Column, data []interface{}) []interface{} {\n\t\treturn data\n\t}\n\n\treturn d.write(ctx, tx, tbl, cols, values, true, false, buildQueryFn, buildArgsFn)\n}\n\nfunc (d *database) Update(ctx context.Context, tx *transaction, tbl string, cols []string, values []*structpb.ListValue) error {\n\tbuildQueryFn := func(table *Table, columns []*Column) string {\n\t\tassigns := make([]string, 0, len(cols))\n\t\tfor _, c := range columns {\n\t\t\tif c.isPrimaryKey {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tassigns = append(assigns, fmt.Sprintf(\"%s = ?\", QuoteString(c.Name())))\n\t\t}\n\n\t\t// If no columns to be updated exist, it should be no-op.\n\t\tif len(assigns) == 0 {\n\t\t\treturn \"\"\n\t\t}\n\n\t\tsetClause := strings.Join(assigns, \", \")\n\n\t\tpKeysNames := table.primaryKey.IndexColumnNames()\n\t\tpkeysAssign := make([]string, len(pKeysNames))\n\t\tfor i, col := range pKeysNames {\n\t\t\tpkeysAssign[i] = fmt.Sprintf(\"%s = ?\", QuoteString(col))\n\t\t}\n\t\twhereClause := strings.Join(pkeysAssign, \" AND \")\n\n\t\treturn fmt.Sprintf(`UPDATE %s SET %s WHERE %s`, QuoteString(tbl), setClause, whereClause)\n\t}\n\n\tbuildArgsFn := func(table *Table, columns []*Column, data []interface{}) []interface{} {\n\t\tnumPKeys := len(table.primaryKey.IndexColumns())\n\t\tvalues := make([]interface{}, 0, len(cols))\n\t\tpkeyValues := make([]interface{}, numPKeys)\n\n\t\tfor i, column := range columns {\n\t\t\tif column.isPrimaryKey {\n\t\t\t\tpkeyValues[column.primaryKeyPos-1] = data[i]\n\t\t\t} else {\n\t\t\t\tvalues = append(values, data[i])\n\t\t\t}\n\t\t}\n\n\t\t// First N(size=columns-pkeys) values are for SET clause\n\t\t// Last M(size=pkeys) values are for WHERE clause\n\t\tvalues = append(values, pkeyValues...)\n\n\t\treturn values\n\t}\n\n\treturn d.write(ctx, tx, tbl, cols, values, false, true, buildQueryFn, buildArgsFn)\n}\n\nfunc (d *database) Replace(ctx context.Context, tx *transaction, tbl string, cols []string, values []*structpb.ListValue) error {\n\tbuildQueryFn := func(table *Table, columns []*Column) string {\n\t\tcolumnName := strings.Join(QuoteStringSlice(cols), \", \")\n\t\tplaceholder := \"?\"\n\t\tif len(cols) > 1 {\n\t\t\tplaceholder += strings.Repeat(\", ?\", len(cols)-1)\n\t\t}\n\t\treturn fmt.Sprintf(`REPLACE INTO %s (%s) VALUES (%s)`, QuoteString(tbl), columnName, placeholder)\n\t}\n\n\tbuildArgsFn := func(table *Table, columns []*Column, data []interface{}) []interface{} {\n\t\treturn data\n\t}\n\n\treturn d.write(ctx, tx, tbl, cols, values, true, false, buildQueryFn, buildArgsFn)\n}\n\nfunc (d *database) InsertOrUpdate(ctx context.Context, tx *transaction, tbl string, cols []string, values []*structpb.ListValue) error {\n\tbuildQueryFn := func(table *Table, columns []*Column) string {\n\t\tassigns := make([]string, 0, len(cols))\n\t\tfor _, c := range columns {\n\t\t\tif c.isPrimaryKey {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tassigns = append(assigns, fmt.Sprintf(\"%s = ?\", QuoteString(c.Name())))\n\t\t}\n\t\tsetClause := strings.Join(assigns, \", \")\n\n\t\tpkeysNamesSlice := table.primaryKey.IndexColumnNames()\n\t\tpkeysNames := strings.Join(QuoteStringSlice(pkeysNamesSlice), \", \")\n\n\t\tcolumnName := strings.Join(QuoteStringSlice(cols), \", \")\n\t\tplaceholder := \"?\"\n\t\tif len(cols) > 1 {\n\t\t\tplaceholder += strings.Repeat(\", ?\", len(cols)-1)\n\t\t}\n\n\t\treturn fmt.Sprintf(`INSERT INTO %s (%s) VALUES (%s) ON CONFLICT (%s) DO UPDATE SET %s`,\n\t\t\tQuoteString(tbl), columnName, placeholder, pkeysNames, setClause)\n\t}\n\n\tbuildArgsFn := func(table *Table, columns []*Column, data []interface{}) []interface{} {\n\t\tsetValues := make([]interface{}, 0, len(cols))\n\n\t\tfor i, column := range columns {\n\t\t\tif !column.isPrimaryKey {\n\t\t\t\tsetValues = append(setValues, data[i])\n\t\t\t}\n\t\t}\n\n\t\t// First N(size=columns) values are for VALUES clause\n\t\t// Last M(size=columns-pkeys) values are for SET clause\n\t\tdata = append(data, setValues...)\n\t\treturn data\n\t}\n\n\treturn d.write(ctx, tx, tbl, cols, values, false, false, buildQueryFn, buildArgsFn)\n}\n\nfunc (d *database) Delete(ctx context.Context, tx *transaction, tbl string, keyset *KeySet) error {\n\tif err := d.waitUntilWritable(ctx, tx); err != nil {\n\t\treturn err\n\t}\n\n\ttable, err := d.useTableExclusive(tbl, tx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tindex := table.primaryKey\n\n\tindexColumnsName := strings.Join(QuoteStringSlice(index.IndexColumnNames()), \", \")\n\tindexColumns := index.IndexColumns()\n\tindexColumnDirs := index.IndexColumnDirections()\n\n\twhereClause, args, err := buildWhereClauseFromKeySet(keyset, indexColumnsName, indexColumns, indexColumnDirs)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = tx.WriteTransaction(func(dbtx databaseWriter) error {\n\t\tquery := fmt.Sprintf(\"DELETE FROM %s %s\", QuoteString(tbl), whereClause)\n\t\tif _, err := dbtx.ExecContext(ctx, query, args...); err != nil {\n\t\t\treturn status.Errorf(codes.Unknown, \"failed to delete: %v\", err)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (d *database) BeginTransaction(tx *transaction) error {\n\tif tx == nil {\n\t\treturn fmt.Errorf(\"invalid transaction: nil\")\n\t}\n\n\tdbtx, err := d.db.BeginTx(tx.Context(), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\td.transactionsMu.Lock()\n\tdefer d.transactionsMu.Unlock()\n\n\tif err := tx.SetTransaction(dbtx, d.endTransaction); err != nil {\n\t\tdbtx.Rollback()\n\t\treturn err\n\t}\n\n\td.transactions[tx.Name()] = tx\n\n\treturn nil\n}\n\nfunc (d *database) endTransaction(tx *transaction, dbtx *sql.Tx) {\n\tif tx == nil {\n\t\treturn\n\t}\n\n\tif IsDebug() {\n\t\tdefer DebugStartEnd(\"[%s] database.endTransaction\", tx.Name())()\n\t}\n\n\t// always try to do rollback to make sure the transaction finished\n\tif dbtx != nil {\n\t\tvar lastErr error\n\t\tfor i := 0; i < 3; i++ {\n\t\t\terr := dbtx.Rollback()\n\t\t\tif err == nil || err == sql.ErrTxDone {\n\t\t\t\tlastErr = nil\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tlastErr = err\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t}\n\n\t\tif lastErr != nil {\n\t\t\tpanic(fmt.Sprintf(\"endTransaction err: %T %v\", lastErr, lastErr))\n\t\t}\n\t}\n\n\td.transactionsMu.Lock()\n\tdefer d.transactionsMu.Unlock()\n\n\tdelete(d.transactions, tx.Name())\n\n\tfor _, tt := range d.tablesInUse {\n\t\ttt.Release(tx)\n\t}\n\td.releaseWriteLock(tx)\n\n\treturn\n}\n\nfunc (d *database) Commit(tx *transaction) error {\n\tif IsDebug() {\n\t\tdefer DebugStartEnd(\"[%s] database.Commit\", tx.Name())()\n\t}\n\n\td.writeTransactionMu.RLock()\n\tdefer d.writeTransactionMu.RUnlock()\n\n\terr := tx.WriteTransaction(func(dbtx databaseWriter) error {\n\t\tif err := dbtx.Commit(); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to commit: %v\", err)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (d *database) Rollback(tx *transaction) error {\n\tif IsDebug() {\n\t\tdefer DebugStartEnd(\"[%s] database.Rollback\", tx.Name())()\n\t}\n\n\td.writeTransactionMu.RLock()\n\tdefer d.writeTransactionMu.RUnlock()\n\n\terr := tx.WriteTransaction(func(dbtx databaseWriter) error {\n\t\tif err := dbtx.Rollback(); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to rollback: %v\", err)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc joinIdentNames(idents []*ast.Ident) string {\n\tnames := make([]string, 0, len(idents))\n\tfor _, ident := range idents {\n\t\tnames = append(names, ident.Name)\n\t}\n\treturn strings.Join(QuoteStringSlice(names), \",\")\n}\n\nfunc (db *database) CreateTable(ctx context.Context, stmt *ast.CreateTable) error {\n\tif _, ok := db.tables[stmt.Name.Idents[0].Name]; ok {\n\t\treturn fmt.Errorf(\"duplicated table: %v\", stmt.Name.Idents[0].Name)\n\t}\n\n\tt, err := createTableFromAST(stmt)\n\tif err != nil {\n\t\treturn status.Errorf(codes.InvalidArgument, \"CreateTable failed: %v\", err)\n\t}\n\n\tdb.transactionsMu.Lock()\n\tdefer db.transactionsMu.Unlock()\n\n\tdb.tables[stmt.Name.Idents[0].Name] = t\n\tdb.tablesInUseMu.Lock()\n\tdb.tablesInUse[stmt.Name.Idents[0].Name] = newTableTransaction()\n\tdb.tablesInUseMu.Unlock()\n\n\tvar columnDefs []string\n\tfor _, col := range t.columns {\n\t\ts := fmt.Sprintf(\"  %s %s\", QuoteString(col.Name()), col.dbDataType)\n\t\tif !col.nullable {\n\t\t\t// Array data type is not supported for now\n\t\t\t// so values for array data type are handled as null always\n\t\t\tif !col.isArray {\n\t\t\t\ts += \" NOT NULL\"\n\t\t\t}\n\t\t}\n\t\tif col.ast != nil && col.ast.DefaultSemantics != nil {\n\t\t\tif genCol, ok := col.ast.DefaultSemantics.(*ast.GeneratedColumnExpr); ok {\n\t\t\t\ts += fmt.Sprintf(\" %s\", genCol.SQL())\n\t\t\t}\n\t\t}\n\t\tif col.ast != nil && col.ast.DefaultSemantics != nil {\n\t\t\tif defExpr, ok := col.ast.DefaultSemantics.(*ast.ColumnDefaultExpr); ok {\n\t\t\t\tif _, ok := defExpr.Expr.(*ast.ArrayLiteral); ok {\n\t\t\t\t\t// TODO: support array literal for default expression\n\t\t\t\t\ts += ` DEFAULT \"[]\"`\n\t\t\t\t} else {\n\t\t\t\t\tdefSQL := defExpr.Expr.SQL()\n\n\t\t\t\t\t// workaround: convert default expression for sqlite\n\t\t\t\t\tswitch exp := defExpr.Expr.(type) {\n\t\t\t\t\tcase *ast.CallExpr:\n\t\t\t\t\t\tswitch strings.ToUpper(exp.Func.Idents[0].Name) {\n\t\t\t\t\t\tcase \"CURRENT_TIMESTAMP\":\n\t\t\t\t\t\t\tdefSQL = \"CURRENT_TIMESTAMP\"\n\t\t\t\t\t\tcase \"CURRENT_DATE\":\n\t\t\t\t\t\t\tdefSQL = \"CURRENT_DATE\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\ts += fmt.Sprintf(\" DEFAULT %s\", defSQL)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif col.valueType.Code == TCString && col.isSized && !col.isMax {\n\t\t\ts += fmt.Sprintf(\" CHECK(LENGTH(%s) <= %d)\", QuoteString(col.Name()), col.size)\n\t\t}\n\t\tcolumnDefs = append(columnDefs, s)\n\t}\n\tcolumnDefsQuery := strings.Join(columnDefs, \",\\n\")\n\tprimaryKeysQuery := strings.Join(QuoteStringSlice(t.primaryKey.IndexColumnNames()), \", \")\n\n\tconstraints := \"\"\n\tif stmt.Cluster != nil {\n\t\tparentTableName := stmt.Cluster.TableName.Idents[0].Name\n\t\tparentStmt, ok := db.tables[parentTableName]\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"could not find parent table for interleaving: %v\", stmt.Name.Idents[0].Name)\n\t\t}\n\n\t\tcolumns := strings.Join(QuoteStringSlice(parentStmt.primaryKey.IndexColumnNames()), \",\")\n\n\t\tconstraints += fmt.Sprintf(\",\\n FOREIGN KEY(%s) REFERENCES %s(%s) %s\", columns, QuoteString(parentTableName), columns, stmt.Cluster.OnDelete)\n\t}\n\n\tfor _, cnst := range stmt.TableConstraints {\n\t\tswitch cnst := cnst.Constraint.(type) {\n\t\tcase *ast.ForeignKey:\n\t\t\tcolumns := joinIdentNames(cnst.Columns)\n\t\t\trefColumns := joinIdentNames(cnst.ReferenceColumns)\n\t\t\tconstraints += fmt.Sprintf(\n\t\t\t\t\",\\n FOREIGN KEY(%s) REFERENCES %s(%s) %s\",\n\t\t\t\tcolumns, QuoteString(cnst.ReferenceTable.Idents[0].Name), refColumns, cnst.OnDelete,\n\t\t\t)\n\t\tcase *ast.Check:\n\t\t\t// TODO: implement, currently just it is ignored.\n\t\t\t// following kind code is needed for this.\n\t\t\t// constraints += fmt.Sprintf(\",\\n CHECK(%s)\", QueryBuilder{}.buildExpr(cnst.Expr).Raw)\n\t\t}\n\t}\n\n\tquery := fmt.Sprintf(\"CREATE TABLE %s (\\n%s,\\n  PRIMARY KEY (%s)%s\\n)\", QuoteString(t.Name), columnDefsQuery, primaryKeysQuery, constraints)\n\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\treturn fmt.Errorf(\"failed to create table for %s: %v\", t.Name, err)\n\t}\n\n\t// register the table information INFORMATION_SCHEMA.TABLES exept for handy-spanner preserved tables\n\tif !strings.HasPrefix(t.Name, \"__\") {\n\t\tif err := db.registerInformationSchemaTables(ctx, stmt); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (db *database) registerInformationSchemaTables(ctx context.Context, stmt *ast.CreateTable) error {\n\ttable, ok := db.tables[stmt.Name.Idents[0].Name]\n\tif !ok {\n\t\treturn fmt.Errorf(\"unexpected error: table not found: %v\", stmt.Name.Idents[0].Name)\n\t}\n\n\tschemaName := \"\"\n\ttableName := stmt.Name.Idents[0].Name\n\tif schema, ok := metaTablesReverseMap[tableName]; ok {\n\t\tschemaName = schema[0]\n\t\ttableName = schema[1]\n\t}\n\n\tparentTableName := \"NULL\"\n\tonDeleteAction := \"NULL\"\n\tstate := \"NULL\"\n\tif schemaName == \"\" {\n\t\tstate = `\"COMMITTED\"`\n\t}\n\n\tif stmt.Cluster != nil {\n\t\tparentTableName = fmt.Sprintf(\"%q\", stmt.Cluster.TableName.Idents[0].Name)\n\n\t\tswitch stmt.Cluster.OnDelete {\n\t\tcase ast.OnDeleteCascade:\n\t\t\tonDeleteAction = `\"CASCADE\"`\n\t\tcase ast.OnDeleteNoAction:\n\t\t\tonDeleteAction = `\"NO ACTION\"`\n\t\t}\n\t}\n\n\tfor i, tcnst := range stmt.TableConstraints {\n\t\tswitch cnst := tcnst.Constraint.(type) {\n\t\tcase *ast.ForeignKey:\n\t\t\tvar onDeleteAction string\n\t\t\tvar constraintName string\n\t\t\tswitch cnst.OnDelete {\n\t\t\tcase ast.OnDeleteCascade:\n\t\t\t\tonDeleteAction = `\"CASCADE\"`\n\t\t\tdefault:\n\t\t\t\tonDeleteAction = `\"NO ACTION\"`\n\t\t\t}\n\t\t\tif tcnst.Name != nil {\n\t\t\t\tconstraintName = tcnst.Name.Name\n\t\t\t} else {\n\t\t\t\tconstraintName = fmt.Sprintf(\"FK_%s_%s_%d\", tableName, cnst.ReferenceTable.Idents[0].Name, i)\n\t\t\t}\n\t\t\tquery := fmt.Sprintf(\n\t\t\t\t`INSERT INTO __INFORMATION_SCHEMA__REFERENTIAL_CONSTRAINTS VALUES(\"\", \"\", %q, \"\", \"\", %q, \"SIMPLE\", \"NO ACTION\", %s, \"COMMITTED\")`,\n\t\t\t\tconstraintName, cnst.ReferenceTable.Idents[0].Name, onDeleteAction,\n\t\t\t)\n\t\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to insert into INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS for table %s: %v\", tableName, err)\n\t\t\t}\n\t\tcase *ast.Check:\n\t\t\t// TODO: implement\n\t\t}\n\t}\n\n\t// register INFORMATION_SCHEMA.TABLES\n\tquery := fmt.Sprintf(`INSERT INTO __INFORMATION_SCHEMA__TABLES VALUES(\"\", %q, %q, %s, %s, %s)`,\n\t\tschemaName, tableName, parentTableName, onDeleteAction, state)\n\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\treturn fmt.Errorf(\"failed to insert into INFORMATION_SCHEMA.TABLES for table %s: %v\", tableName, err)\n\t}\n\n\t// register meta schema for columns\n\tfor pos, column := range stmt.Columns {\n\t\tnullable := \"YES\"\n\t\tif column.NotNull {\n\t\t\tnullable = \"NO\"\n\t\t}\n\t\tspannerType := schemaTypetoTypString(column.Type)\n\n\t\t// register INFORMATION_SCHEMA.COLUMNS\n\t\tquery := fmt.Sprintf(`INSERT INTO __INFORMATION_SCHEMA__COLUMNS VALUES(\"\", %q, %q, %q, %d, NULL, NULL, %q, %q)`,\n\t\t\tschemaName, tableName, column.Name.Name,\n\t\t\tpos+1, nullable, spannerType)\n\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to insert into INFORMATION_SCHEMA.COLUMNS for table %s: %v\", tableName, err)\n\t\t}\n\n\t\t// register meta schema for column options\n\t\tif column.Options != nil {\n\t\t\tvar hasAllowCommitTimestamp bool\n\t\t\tif val, err := column.Options.BoolField(\"allow_commit_timestamp\"); err == nil && val != nil {\n\t\t\t\thasAllowCommitTimestamp = *val\n\t\t\t}\n\t\t\tif hasAllowCommitTimestamp {\n\n\t\t\t\t// register INFORMATION_SCHEMA.COLUMN_OPTIONS for `allow_commit_timestamp` option\n\t\t\t\tquery := fmt.Sprintf(`INSERT INTO __INFORMATION_SCHEMA__COLUMN_OPTIONS VALUES(\"\", %q, %q, %q, %q, %q, %q)`,\n\t\t\t\t\tschemaName, tableName, column.Name.Name,\n\t\t\t\t\t\"allow_commit_timestamp\", \"BOOL\", \"TRUE\")\n\t\t\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to insert into INFORMATION_SCHEMA.COLUMN_OPTIONS for table %s: %v\", tableName, err)\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\t}\n\n\tif err := db.registerInformationSchemaIndexes(ctx, table, &ast.CreateIndex{\n\t\tUnique:       true,\n\t\tNullFiltered: false,\n\t\tName:         &ast.Path{Idents: []*ast.Ident{{Name: \"PRIMARY_KEY\"}}},\n\t\tTableName:    &ast.Path{Idents: []*ast.Ident{{Name: stmt.Name.Idents[0].Name}}},\n\t\tKeys:         stmt.PrimaryKeys,\n\t\tStoring:      nil,\n\t\tInterleaveIn: nil,\n\t}); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (db *database) CreateIndex(ctx context.Context, stmt *ast.CreateIndex) error {\n\ttable, ok := db.tables[stmt.TableName.Idents[0].Name]\n\tif !ok {\n\t\treturn fmt.Errorf(\"table does not exist: %v\", stmt.Name.Idents[0].Name)\n\t}\n\n\tindex, err := table.createIndex(stmt)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tidxType := \"INDEX\"\n\tif index.unique {\n\t\tidxType = \"UNIQUE INDEX\"\n\t}\n\tcolumnsName := strings.Join(QuoteStringSlice(index.IndexColumnNames()), \", \")\n\n\tquery := fmt.Sprintf(\"CREATE %s %s ON %s (%s)\", idxType, QuoteString(index.Name()), QuoteString(table.Name), columnsName)\n\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\treturn fmt.Errorf(\"failed to create index for %s: %v\", index.Name(), err)\n\t}\n\n\tif err := db.registerInformationSchemaIndexes(ctx, table, stmt); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (db *database) registerInformationSchemaIndexes(ctx context.Context, table *Table, stmt *ast.CreateIndex) error {\n\tschemaName := \"\"\n\ttableName := stmt.TableName.Idents[0].Name\n\tif schema, ok := metaTablesReverseMap[tableName]; ok {\n\t\tschemaName = schema[0]\n\t\ttableName = schema[1]\n\t}\n\n\tindexName := stmt.Name.Idents[0].Name\n\tisUnique := \"FALSE\"\n\tif stmt.Unique {\n\t\tisUnique = \"TRUE\"\n\t}\n\tisNullFiltered := \"FALSE\"\n\tif stmt.NullFiltered {\n\t\tisNullFiltered = \"TRUE\"\n\t}\n\n\tindexType := stmt.Name.Idents[0].Name\n\tindexState := \"NULL\"\n\tif indexName != \"PRIMARY_KEY\" {\n\t\tindexType = \"INDEX\"\n\t\tindexState = `\"READ_WRITE\"`\n\t}\n\tmanaged := \"FALSE\" // fixed\n\tparentTableName := \"\"\n\tif stmt.InterleaveIn != nil {\n\t\tparentTableName = stmt.InterleaveIn.TableName.Name\n\t}\n\n\t// register INFORMATION_SCHEMA.INDEXES\n\tquery := fmt.Sprintf(`INSERT INTO __INFORMATION_SCHEMA__INDEXES VALUES(\"\", %q, %q, %q, %q, %q, %s, %s, %s, %s)`,\n\t\tschemaName, tableName, indexName, indexType,\n\t\tparentTableName, isUnique, isNullFiltered, indexState, managed)\n\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\treturn fmt.Errorf(\"failed to insert into INFORMATION_SCHEMA.INDEXES for table %s: %v\", tableName, err)\n\t}\n\n\tfor pos, key := range stmt.Keys {\n\t\tcolumnName := key.Name.Name\n\t\tcolumn, err := table.getColumn(columnName)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tordering := \"ASC\"\n\t\tswitch key.Dir {\n\t\tcase ast.DirectionAsc:\n\t\t\tordering = \"ASC\"\n\t\tcase ast.DirectionDesc:\n\t\t\tordering = \"DESC\"\n\t\t}\n\n\t\tisNullable := \"NO\"\n\t\tif column.nullable {\n\t\t\tisNullable = \"YES\"\n\t\t}\n\t\tspannerType := \"\"\n\t\tif column.ast != nil {\n\t\t\tspannerType = schemaTypetoTypString(column.ast.Type)\n\t\t}\n\n\t\t// register INFORMATION_SCHEMA.INDEX_COLUMNS\n\t\tquery := fmt.Sprintf(`INSERT INTO __INFORMATION_SCHEMA__INDEX_COLUMNS VALUES(\"\", %q, %q, %q, %q, %q, %d, %q, %q, %q)`,\n\t\t\tschemaName, tableName, indexName, indexType, columnName,\n\t\t\tpos+1, ordering, isNullable, spannerType)\n\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to insert into INFORMATION_SCHEMA.INDEX_COLUMNS for table %s: %v\", tableName, err)\n\t\t}\n\t}\n\n\tif stmt.Storing != nil {\n\t\tfor _, ident := range stmt.Storing.Columns {\n\t\t\tcolumnName := ident.Name\n\t\t\tcolumn, err := table.getColumn(columnName)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\tisNullable := \"NO\"\n\t\t\tif column.nullable {\n\t\t\t\tisNullable = \"YES\"\n\t\t\t}\n\t\t\tspannerType := \"\"\n\t\t\tif column.ast != nil {\n\t\t\t\tspannerType = schemaTypetoTypString(column.ast.Type)\n\t\t\t}\n\n\t\t\t// register INFORMATION_SCHEMA.INDEX_COLUMNS\n\t\t\tquery := fmt.Sprintf(`INSERT INTO __INFORMATION_SCHEMA__INDEX_COLUMNS VALUES(\"\", %q, %q, %q, %q, %q, %s, %s, %q, %q)`,\n\t\t\t\tschemaName, tableName, indexName, indexType, columnName,\n\t\t\t\t\"NULL\", \"NULL\", isNullable, spannerType)\n\t\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to insert into INFORMATION_SCHEMA.INDEX_COLUMNS for table %s: %v\", tableName, err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (d *database) Close() error {\n\td.cancel()\n\n\tif d.db == nil {\n\t\treturn nil\n\t}\n\n\td.schemaMu.Lock()\n\tdefer d.schemaMu.Unlock()\n\n\tif err := d.db.Close(); err != nil {\n\t\treturn err\n\t}\n\n\td.db = nil\n\n\treturn nil\n}\n\nfunc QuoteString(s string) string {\n\treturn fmt.Sprintf(\"`%s`\", s)\n}\n\nfunc QuoteStringSlice(ss []string) []string {\n\tss2 := make([]string, len(ss))\n\tfor i := range ss {\n\t\tss2[i] = QuoteString(ss[i])\n\t}\n\treturn ss2\n}\n"
  },
  {
    "path": "server/database_function.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"math\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dgryski/go-farm\"\n\tsqlite \"github.com/mattn/go-sqlite3\"\n)\n\nfunc init() {\n\tsql.Register(\"sqlite3_spanner\", &sqlite.SQLiteDriver{\n\t\tConnectHook: func(conn *sqlite.SQLiteConn) error {\n\t\t\tfor name, fn := range customFunctions {\n\t\t\t\tname = strings.ToLower(name)\n\t\t\t\tif fn.Func == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif err := conn.RegisterFunc(name, fn.Func, true); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t})\n}\n\ntype CustomFunction struct {\n\tName       string\n\tFunc       interface{}\n\tNArgs      int\n\tArgTypes   func([]ValueType) bool\n\tReturnType func([]ValueType) ValueType\n}\n\nfunc exactMatchArgumentTypes(vts ...ValueType) func([]ValueType) bool {\n\treturn func(vts2 []ValueType) bool {\n\t\tif len(vts) != len(vts2) {\n\t\t\treturn false\n\t\t}\n\t\tfor i := range vts {\n\t\t\tok := compareValueType(vts[i], vts2[i])\n\t\t\tif !ok {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\n\t\treturn true\n\t}\n}\n\nfunc staticReturnType(vt ValueType) func([]ValueType) ValueType {\n\treturn func([]ValueType) ValueType {\n\t\treturn vt\n\t}\n}\n\nconst SqliteArgumentRuntimeErrorPrefix = \"_sqlite_argument_runtime_error_: \"\nconst SqliteOutOfRangeRuntimeErrorPrefix = \"_sqlite_ooo_runtime_error_: \"\n\n// sqliteArgumentRuntimeError is runtime error to run query in sqlite.\n// Runtime error only can be returned as string of error in sqlite.\n// To distinguish error as InvalidArgument or InternalError, the error message\n// by this has a specific prefix. RowsIterator checks the prefix and returns the error\n// as InvalidArgument.\ntype sqliteArgumentRuntimeError struct {\n\tmsg string\n}\n\nfunc (e *sqliteArgumentRuntimeError) Error() string {\n\treturn SqliteArgumentRuntimeErrorPrefix + e.msg\n}\n\ntype sqliteOutOfRangeRuntimeError struct {\n\tmsg string\n}\n\nfunc (e *sqliteOutOfRangeRuntimeError) Error() string {\n\treturn SqliteOutOfRangeRuntimeErrorPrefix + e.msg\n}\n\n// customFunctionNamesMap is naming map from Spanner's function to sqlite's custom function.\n// This is used to avoid conflict of reserved name in sqlite.\nvar customFunctionNamesMap map[string]string = map[string]string{\n\t\"CURRENT_TIMESTAMP\": \"___CURRENT_TIMESTAMP\",\n}\n\n// customFunctions is functions for spanner.\n//\n// sqlite cannot register function which returns interface{}.\n// If spanner function may return different type value, we need to register multiple functions\n// for each returned type.\nvar customFunctions map[string]CustomFunction = map[string]CustomFunction{\n\t\"SIGN\": {\n\t\tFunc:  sqlite3FnSign,\n\t\tNArgs: 1,\n\t\tArgTypes: exactMatchArgumentTypes(\n\t\t\tValueType{Code: TCInt64},\n\t\t),\n\t\tReturnType: staticReturnType(ValueType{\n\t\t\tCode: TCInt64,\n\t\t}),\n\t},\n\t\"STARTS_WITH\": {\n\t\tFunc:  sqlite3FnStartsWith,\n\t\tNArgs: 2,\n\t\tArgTypes: exactMatchArgumentTypes(\n\t\t\tValueType{Code: TCString},\n\t\t\tValueType{Code: TCString},\n\t\t),\n\t\tReturnType: staticReturnType(ValueType{\n\t\t\tCode: TCBool,\n\t\t}),\n\t},\n\t\"ENDS_WITH\": {\n\t\tFunc:  sqlite3FnEndsWith,\n\t\tNArgs: 2,\n\t\tArgTypes: exactMatchArgumentTypes(\n\t\t\tValueType{Code: TCString},\n\t\t\tValueType{Code: TCString},\n\t\t),\n\t\tReturnType: staticReturnType(ValueType{\n\t\t\tCode: TCBool,\n\t\t}),\n\t},\n\t\"CONCAT\": {\n\t\tFunc:  sqlite3FnConcat,\n\t\tNArgs: -1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\tif len(vts) == 0 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tvt := vts[0]\n\t\t\tfor i := range vts {\n\t\t\t\tif !compareValueType(vt, vts[i]) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\tif vt.Code != TCString && vt.Code != TCBytes {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn vts[0]\n\t\t},\n\t},\n\n\t\"COUNT\": {\n\t\tFunc:  nil,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn true\n\t\t},\n\t\tReturnType: staticReturnType(ValueType{\n\t\t\tCode: TCInt64,\n\t\t}),\n\t},\n\t\"MAX\": {\n\t\tFunc:  nil,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\tcode := vts[0].Code\n\t\t\tif code == TCArray || code == TCStruct {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn vts[0]\n\t\t},\n\t},\n\t\"MIN\": {\n\t\tFunc:  nil,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\tcode := vts[0].Code\n\t\t\tif code == TCArray || code == TCStruct {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn vts[0]\n\t\t},\n\t},\n\t\"AVG\": {\n\t\tFunc:  nil,\n\t\tNArgs: -1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\tfor _, vt := range vts {\n\t\t\t\tif vt.Code != TCInt64 && vt.Code == TCFloat64 {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t\tReturnType: staticReturnType(ValueType{\n\t\t\tCode: TCFloat64,\n\t\t}),\n\t},\n\t\"SUM\": {\n\t\tFunc:  nil,\n\t\tNArgs: -1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\tfor _, vt := range vts {\n\t\t\t\tif vt.Code != TCInt64 && vt.Code == TCFloat64 {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\tfor _, vt := range vts {\n\t\t\t\tif vt.Code == TCFloat64 {\n\t\t\t\t\treturn ValueType{Code: TCFloat64}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn ValueType{Code: TCInt64}\n\t\t},\n\t},\n\n\t\"___EXTRACT_FROM_TIMESTAMP\": {\n\t\tFunc:  sqlite3FnExtractFromTimestamp,\n\t\tNArgs: 3,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCString &&\n\t\t\t\tvts[1].Code == TCTimestamp &&\n\t\t\t\tvts[2].Code == TCString\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCInt64}\n\t\t},\n\t},\n\t\"___EXTRACT_FROM_DATE\": {\n\t\tFunc:  sqlite3FnExtractFromDate,\n\t\tNArgs: 2,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCString && vts[1].Code == TCDate\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCInt64}\n\t\t},\n\t},\n\n\t\"___CAST_INT64_TO_BOOL\": {\n\t\tFunc:  sqlite3FnCastInt64ToBool,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCInt64\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCBool}\n\t\t},\n\t},\n\t\"___CAST_INT64_TO_STRING\": {\n\t\tFunc:  sqlite3FnCastInt64ToString,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCInt64\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCString}\n\t\t},\n\t},\n\t\"___CAST_INT64_TO_FLOAT64\": {\n\t\tFunc:  sqlite3FnCastInt64ToFloat64,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCInt64\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCFloat64}\n\t\t},\n\t},\n\t\"___CAST_FLOAT64_TO_STRING\": {\n\t\tFunc:  sqlite3FnCastFloat64ToString,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCFloat64\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCString}\n\t\t},\n\t},\n\t\"___CAST_FLOAT64_TO_INT64\": {\n\t\tFunc:  sqlite3FnCastFloat64ToInt64,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCFloat64\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCInt64}\n\t\t},\n\t},\n\t\"___CAST_BOOL_TO_STRING\": {\n\t\tFunc:  sqlite3FnCastBoolToString,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCBool\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCString}\n\t\t},\n\t},\n\t\"___CAST_BOOL_TO_INT64\": {\n\t\tFunc:  sqlite3FnCastBoolToInt64,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCBool\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCInt64}\n\t\t},\n\t},\n\t\"___CAST_STRING_TO_BOOL\": {\n\t\tFunc:  sqlite3FnCastStringToBool,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCString\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCBool}\n\t\t},\n\t},\n\t\"___CAST_STRING_TO_INT64\": {\n\t\tFunc:  sqlite3FnCastStringToInt64,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCString\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCInt64}\n\t\t},\n\t},\n\t\"___CAST_STRING_TO_FLOAT64\": {\n\t\tFunc:  sqlite3FnCastStringToFloat64,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCString\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCFloat64}\n\t\t},\n\t},\n\t\"___CAST_STRING_TO_DATE\": {\n\t\tFunc:  sqlite3FnCastStringToDate,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCString\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCDate}\n\t\t},\n\t},\n\t\"___CAST_STRING_TO_TIMESTAMP\": {\n\t\tFunc:  sqlite3FnCastStringToTimestamp,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCString\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCTimestamp}\n\t\t},\n\t},\n\t\"___CAST_DATE_TO_STRING\": {\n\t\tFunc:  sqlite3FnCastDateToString,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCDate\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCString}\n\t\t},\n\t},\n\t\"___CAST_DATE_TO_TIMESTAMP\": {\n\t\tFunc:  sqlite3FnCastDateToTimestamp,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCDate\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCTimestamp}\n\t\t},\n\t},\n\t\"___CAST_TIMESTAMP_TO_STRING\": {\n\t\tFunc:  sqlite3FnCastTimestampToString,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCTimestamp\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCString}\n\t\t},\n\t},\n\t\"___CAST_TIMESTAMP_TO_DATE\": {\n\t\tFunc:  sqlite3FnCastTimestampToDate,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCTimestamp\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCDate}\n\t\t},\n\t},\n\t\"PENDING_COMMIT_TIMESTAMP\": getCustomFunctionForCurrentTime(),\n\t\"___CURRENT_TIMESTAMP\":     getCustomFunctionForCurrentTime(),\n\t\"IFNULL\": {\n\t\tFunc:  nil,\n\t\tNArgs: 2,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\tif len(vts) == 0 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tvt := vts[0]\n\t\t\tfor i := range vts {\n\t\t\t\tif !compareValueType(vt, vts[i]) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn vts[0]\n\t\t},\n\t},\n\t\"NULLIF\": {\n\t\tFunc:  nil,\n\t\tNArgs: 2,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\tif len(vts) == 0 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tvt := vts[0]\n\t\t\tfor i := range vts {\n\t\t\t\tif !compareValueType(vt, vts[i]) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn vts[0]\n\t\t},\n\t},\n\t\"FARM_FINGERPRINT\": {\n\t\tFunc:  sqlite3FnFarmFingerprint,\n\t\tNArgs: 1,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn vts[0].Code == TCString\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCInt64}\n\t\t},\n\t},\n\t\"MOD\": {\n\t\tFunc:  sqlite3FnMod,\n\t\tNArgs: 2,\n\t\tArgTypes: exactMatchArgumentTypes(\n\t\t\tValueType{Code: TCInt64},\n\t\t\tValueType{Code: TCInt64},\n\t\t),\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCInt64}\n\t\t},\n\t},\n\t\"GENERATE_ARRAY\": {\n\t\tFunc:  sqlite3FnGenerateArray,\n\t\tNArgs: -2,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\tif len(vts) < 2 || len(vts) > 3 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif vts[0].Code == TCInt64 && vts[1].Code == TCInt64 {\n\t\t\t\tif len(vts) == 3 {\n\t\t\t\t\treturn vts[2].Code == TCInt64\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &ValueType{Code: TCInt64},\n\t\t\t}\n\t\t},\n\t},\n}\n\nfunc sqlite3FnGenerateArray(xs ...int64) []byte {\n\tstep := int64(1)\n\tif len(xs) == 3 {\n\t\tstep = xs[2]\n\t}\n\ta, b := xs[0], xs[1]\n\tres := make([]byte, 0, b-a)\n\tfor i := a; i <= b; i += step {\n\t\tres = append(res, byte(i))\n\t}\n\treturn res\n}\n\nfunc sqlite3FnMod(a, b int64) int64 {\n\treturn a % b\n}\n\nfunc sqlite3FnFarmFingerprint(s string) int64 {\n\treturn int64(farm.Fingerprint64([]byte(s)))\n}\n\nfunc sqlite3FnSign(x int64) int64 {\n\tif x > 0 {\n\t\treturn 1\n\t}\n\tif x < 0 {\n\t\treturn -1\n\t}\n\treturn 0\n}\n\nfunc sqlite3FnStartsWith(a, b string) bool {\n\treturn strings.HasPrefix(a, b)\n}\n\nfunc sqlite3FnEndsWith(a, b string) bool {\n\treturn strings.HasSuffix(a, b)\n}\n\nfunc sqlite3FnConcat(xs ...string) string {\n\treturn strings.Join(xs, \"\")\n}\n\n// sqlite3FnExtractFromTimestamp is simulation function of EXTRACT.\n// It supports except DATE part.\nfunc sqlite3FnExtractFromTimestamp(part string, timestamp string, tz string) (int64, error) {\n\ttzErr := &sqliteOutOfRangeRuntimeError{msg: fmt.Sprintf(\"Invalid time zone: %s\", tz)}\n\tif len(tz) == 0 {\n\t\treturn 0, tzErr\n\t}\n\n\tvar location *time.Location\n\n\t// (+|-)H[H][:M[M]]\n\tif tz[0] == '-' || tz[0] == '+' {\n\t\ts := tz\n\t\tvar neg bool\n\t\tif s[0] == '-' {\n\t\t\tneg = true\n\t\t}\n\n\t\ts = s[1:]\n\n\t\tvar colonFound bool\n\t\thour := -1\n\t\tmin := 0\n\t\tfor _, c := range s {\n\t\t\tif c == ':' {\n\t\t\t\tmin = -1\n\t\t\t\tcolonFound = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif c < '0' && '9' < c {\n\t\t\t\treturn 0, tzErr\n\t\t\t}\n\n\t\t\tn := int(c - '0')\n\n\t\t\tif colonFound {\n\t\t\t\tif min == -1 {\n\t\t\t\t\tmin = n\n\t\t\t\t} else {\n\t\t\t\t\tmin = 10*min + n\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif hour == -1 {\n\t\t\t\t\thour = n\n\t\t\t\t} else {\n\t\t\t\t\thour = 10*hour + n\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif hour == -1 || min == -1 {\n\t\t\treturn 0, tzErr\n\t\t}\n\t\tif hour > 24 || min > 60 {\n\t\t\treturn 0, tzErr\n\t\t}\n\n\t\toffset := hour*3600 + min*60\n\t\tif neg {\n\t\t\toffset = -offset\n\t\t}\n\t\tlocation = time.FixedZone(tz, offset)\n\t} else {\n\t\tloc, err := time.LoadLocation(tz)\n\t\tif err != nil {\n\t\t\treturn 0, tzErr\n\t\t}\n\t\tlocation = loc\n\t}\n\n\tt, err := time.Parse(time.RFC3339Nano, timestamp)\n\tif err != nil {\n\t\t// Must not happen\n\t\treturn 0, fmt.Errorf(\"___EXTRACT_FROM_TIMESTAMP: unexpected format %q as timestamp: %v\", timestamp, err)\n\t}\n\tt = t.In(location)\n\n\tswitch strings.ToUpper(part) {\n\tcase \"NANOSECOND\":\n\t\treturn int64(t.Nanosecond()), nil\n\tcase \"MICROSECOND\":\n\t\treturn int64(t.Nanosecond() / 1000), nil\n\tcase \"MILLISECOND\":\n\t\treturn int64(t.Nanosecond() / 1000000), nil\n\tcase \"SECOND\":\n\t\treturn int64(t.Second()), nil\n\tcase \"MINUTE\":\n\t\treturn int64(t.Minute()), nil\n\tcase \"HOUR\":\n\t\treturn int64(t.Hour()), nil\n\tcase \"DAYOFWEEK\":\n\t\treturn int64(t.Weekday()), nil\n\tcase \"DAY\":\n\t\treturn int64(t.Day()), nil\n\tcase \"DAYOFYEAR\":\n\t\treturn int64(t.YearDay()), nil\n\tcase \"WEEK\":\n\t\t// TODO: calculate week from timestamp\n\t\treturn 0, fmt.Errorf(\"___EXTRACT_FROM_TIMESTAMP: WEEK not supported for now\")\n\tcase \"ISOWEEK\":\n\t\t_, w := t.ISOWeek()\n\t\treturn int64(w), nil\n\tcase \"MONTH\":\n\t\treturn int64(t.Month()), nil\n\tcase \"QUARTER\":\n\t\t// 1 for Jan-Mar, 2 for Apr-Jun, 3 for Jul-Sep, 4 for Oct-Dec\n\t\tm := t.Month()\n\t\treturn int64(m/4 + 1), nil\n\tcase \"YEAR\":\n\t\treturn int64(t.Year()), nil\n\tcase \"ISOYEAR\":\n\t\ty, _ := t.ISOWeek()\n\t\treturn int64(y), nil\n\tdefault:\n\t\t// Must not happen\n\t\treturn 0, fmt.Errorf(\"___EXTRACT_FROM_TIMESTAMP: unexpected part: %s\", part)\n\t}\n}\n\n// sqlite3FnExtractFromDate is simulation function of EXTRACT.\nfunc sqlite3FnExtractFromDate(part string, date string) (int64, error) {\n\tt, err := time.Parse(\"2006-01-02\", date)\n\tif err != nil {\n\t\t// Must not happen\n\t\treturn 0, fmt.Errorf(\"___EXTRACT_FROM_DATE: unexpected format %q as date: %v\", date, err)\n\t}\n\n\tswitch strings.ToUpper(part) {\n\tcase \"DAYOFWEEK\":\n\t\treturn int64(t.Weekday()), nil\n\tcase \"DAY\":\n\t\treturn int64(t.Day()), nil\n\tcase \"DAYOFYEAR\":\n\t\treturn int64(t.YearDay()), nil\n\tcase \"WEEK\":\n\t\t// TODO: calculate week from timestamp\n\t\treturn 0, fmt.Errorf(\"___EXTRACT_FROM_DATE: WEEK not supported for now\")\n\tcase \"ISOWEEK\":\n\t\t_, w := t.ISOWeek()\n\t\treturn int64(w), nil\n\tcase \"MONTH\":\n\t\treturn int64(t.Month()), nil\n\tcase \"QUARTER\":\n\t\t// 1 for Jan-Mar, 2 for Apr-Jun, 3 for Jul-Sep, 4 for Oct-Dec\n\t\tm := t.Month()\n\t\treturn int64(m/4 + 1), nil\n\tcase \"YEAR\":\n\t\treturn int64(t.Year()), nil\n\tcase \"ISOYEAR\":\n\t\ty, _ := t.ISOWeek()\n\t\treturn int64(y), nil\n\tdefault:\n\t\t// Must not happen\n\t\treturn 0, fmt.Errorf(\"___EXTRACT_FROM_DATE: unexpected part: %s\", part)\n\t}\n}\n\nfunc sqlite3FnCastInt64ToBool(i int64) bool {\n\treturn i != 0\n}\n\nfunc sqlite3FnCastInt64ToString(i int64) string {\n\treturn strconv.FormatInt(i, 10)\n}\n\nfunc sqlite3FnCastInt64ToFloat64(i int64) float64 {\n\treturn float64(i)\n}\n\nfunc sqlite3FnCastFloat64ToString(f float64) string {\n\tif math.IsInf(f, 0) {\n\t\tif f < 0 {\n\t\t\treturn \"-inf\"\n\t\t}\n\t\treturn \"inf\"\n\t}\n\n\treturn strconv.FormatFloat(f, 'g', -1, 64)\n}\n\nfunc sqlite3FnCastFloat64ToInt64(f float64) (int64, error) {\n\tif math.IsNaN(f) {\n\t\t// OutOfRange error\n\t\tmsg := \"Illegal conversion of non-finite floating point number to an integer: nan\"\n\t\treturn 0, &sqliteOutOfRangeRuntimeError{msg: msg}\n\t}\n\tif math.IsInf(f, 0) {\n\t\t// OutOfRange error\n\t\tinf := \"inf\"\n\t\tif f < 0 {\n\t\t\tinf = \"-inf\"\n\t\t}\n\t\tmsg := \"Illegal conversion of non-finite floating point number to an integer: \" + inf\n\t\treturn 0, &sqliteOutOfRangeRuntimeError{msg: msg}\n\t}\n\n\tif f < 0 {\n\t\treturn int64(f - 0.5), nil\n\t}\n\treturn int64(f + 0.5), nil\n}\n\nfunc sqlite3FnCastBoolToString(b bool) string {\n\tif b {\n\t\treturn \"TRUE\"\n\t}\n\treturn \"FALSE\"\n}\n\nfunc sqlite3FnCastBoolToInt64(b bool) int64 {\n\tif b {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc sqlite3FnCastStringToBool(s string) (bool, error) {\n\tss := strings.ToUpper(s)\n\tif ss == \"TRUE\" {\n\t\treturn true, nil\n\t}\n\tif ss == \"FALSE\" {\n\t\treturn false, nil\n\t}\n\n\t// OutOfRange error\n\treturn false, &sqliteOutOfRangeRuntimeError{msg: fmt.Sprintf(\"Bad bool value: %s\", s)}\n}\n\nfunc sqlite3FnCastStringToInt64(s string) (int64, error) {\n\tif s == \"\" {\n\t\t// OutOfRange error\n\t\treturn 0, &sqliteOutOfRangeRuntimeError{msg: fmt.Sprintf(\"Bad int64 value: %s\", s)}\n\t}\n\n\torig := s\n\tvar neg bool\n\tif s[0] == '+' {\n\t\ts = s[1:]\n\t} else if s[0] == '-' {\n\t\tneg = true\n\t\ts = s[1:]\n\t}\n\n\t// Base is available only for 10 or 16 in spanner.\n\tbase := 10\n\tif len(s) > 2 && s[0] == '0' && s[1] == 'x' {\n\t\tbase = 16\n\t\ts = s[2:]\n\t}\n\n\tn, err := strconv.ParseInt(s, base, 64)\n\tif err != nil {\n\t\t// OutOfRange error\n\t\treturn 0, &sqliteOutOfRangeRuntimeError{msg: fmt.Sprintf(\"Bad int64 value: %s\", orig)}\n\t}\n\n\tif n < 0 {\n\t\t// OutOfRange error\n\t\treturn 0, &sqliteOutOfRangeRuntimeError{msg: fmt.Sprintf(\"Bad int64 value: %s\", orig)}\n\t}\n\n\tif neg {\n\t\tn = -n\n\t}\n\n\treturn n, nil\n}\n\nfunc sqlite3FnCastStringToFloat64(s string) (float64, error) {\n\tn, err := strconv.ParseFloat(s, 64)\n\tif err != nil {\n\t\t// OutOfRange error\n\t\treturn 0, &sqliteOutOfRangeRuntimeError{msg: fmt.Sprintf(\"Bad double value: %s\", s)}\n\t}\n\n\treturn n, nil\n}\n\nfunc sqlite3FnCastStringToDate(s string) (string, error) {\n\tt, ok := parseDateLiteral(s)\n\tif !ok {\n\t\t// InvalidArgument error\n\t\treturn \"\", &sqliteArgumentRuntimeError{msg: fmt.Sprintf(\"Could not cast literal %q to type DATE\", s)}\n\t}\n\n\treturn t.Format(\"2006-01-02\"), nil\n}\n\nfunc sqlite3FnCastStringToTimestamp(s string) (string, error) {\n\tt, ok := parseTimestampLiteral(s)\n\tif !ok {\n\t\t// InvalidArgument error\n\t\treturn \"\", &sqliteArgumentRuntimeError{msg: fmt.Sprintf(\"Could not cast literal %q to type TIMESTAMP\", s)}\n\t}\n\n\treturn t.Format(time.RFC3339Nano), nil\n}\n\nfunc sqlite3FnCastDateToString(s string) (string, error) {\n\tt, ok := parseDateLiteral(s)\n\tif !ok {\n\t\t// InvalidArgument error\n\t\treturn \"\", &sqliteArgumentRuntimeError{msg: fmt.Sprintf(\"Could not cast literal %q to type STRING\", s)}\n\t}\n\n\treturn t.Format(\"2006-01-02\"), nil\n}\n\nfunc sqlite3FnCastDateToTimestamp(s string) (string, error) {\n\tt, ok := parseDateLiteral(s)\n\tif !ok {\n\t\t// InvalidArgument error\n\t\treturn \"\", &sqliteArgumentRuntimeError{msg: fmt.Sprintf(\"Could not cast literal %q to type TIMESTAMP\", s)}\n\t}\n\n\treturn t.UTC().Format(time.RFC3339Nano), nil\n}\n\nfunc sqlite3FnCastTimestampToString(s string) (string, error) {\n\tt, ok := parseTimestampLiteral(s)\n\tif !ok {\n\t\t// InvalidArgument error\n\t\treturn \"\", &sqliteArgumentRuntimeError{msg: fmt.Sprintf(\"Could not cast literal %q to type STRING\", s)}\n\t}\n\n\treturn t.In(parseLocation).Format(\"2006-01-02 15:04:05.999999999-07\"), nil\n}\n\nfunc sqlite3FnCastTimestampToDate(s string) (string, error) {\n\tt, ok := parseTimestampLiteral(s)\n\tif !ok {\n\t\t// InvalidArgument error\n\t\treturn \"\", &sqliteArgumentRuntimeError{msg: fmt.Sprintf(\"Could not cast literal %q to type DATE\", s)}\n\t}\n\n\treturn t.In(parseLocation).Format(\"2006-01-02\"), nil\n}\n\nfunc sqlite3FnCurrentTimestamp() string {\n\treturn time.Now().UTC().Format(time.RFC3339Nano)\n}\n\nfunc getCustomFunctionForCurrentTime() CustomFunction {\n\treturn CustomFunction{\n\t\tFunc:  sqlite3FnCurrentTimestamp,\n\t\tNArgs: 0,\n\t\tArgTypes: func(vts []ValueType) bool {\n\t\t\treturn true\n\t\t},\n\t\tReturnType: func(vts []ValueType) ValueType {\n\t\t\treturn ValueType{Code: TCTimestamp}\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "server/database_json.go",
    "content": "// Copyright 2020 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// +build json1\n\npackage server\n\nfunc useSqliteJSON() { return }\n"
  },
  {
    "path": "server/database_nonjson.go",
    "content": "// Copyright 2020 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// +build !json1\n\npackage server\n\nfunc useSqliteJSON() { panic(`Require \"json1\" build tag to use some features`) }\n"
  },
  {
    "path": "server/database_query_test.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"context\"\n\t\"math\"\n\t\"regexp\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudspannerecosystem/memefish\"\n\t\"github.com/cloudspannerecosystem/memefish/token\"\n\tcmp \"github.com/google/go-cmp/cmp\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nfunc TestQuery(t *testing.T) {\n\tctx := context.Background()\n\tdb := newDatabase()\n\tfor _, s := range allSchema {\n\t\tddls := parseDDL(t, s)\n\t\tfor _, ddl := range ddls {\n\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t}\n\t}\n\n\tfor _, query := range []string{\n\t\t`INSERT INTO Simple VALUES(100, \"xxx\")`,\n\t\t`INSERT INTO Simple VALUES(200, \"yyy\")`,\n\t\t`INSERT INTO Simple VALUES(300, \"zzz\")`,\n\n\t\t`INSERT INTO CompositePrimaryKeys VALUES(1, \"aaa\", 1, 0, \"x1\", \"y1\", \"z\")`,\n\t\t`INSERT INTO CompositePrimaryKeys VALUES(2, \"bbb\", 2, 0, \"x1\", \"y2\", \"z\")`,\n\t\t`INSERT INTO CompositePrimaryKeys VALUES(3, \"bbb\", 3, 0, \"x1\", \"y3\", \"z\")`,\n\t\t`INSERT INTO CompositePrimaryKeys VALUES(4, \"ccc\", 3, 0, \"x2\", \"y4\", \"z\")`,\n\t\t`INSERT INTO CompositePrimaryKeys VALUES(5, \"ccc\", 4, 0, \"x2\", \"y5\", \"z\")`,\n\n\t\t`INSERT INTO FullTypes VALUES(\"xxx\",\n            \"xxx\", \"xxx\",\n            true, true,\n            \"eHl6\", \"eHl6\",\n            \"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.123456789Z\",\n            100, 100,\n            0.5, 0.5,\n            \"2012-03-04\", \"2012-03-04\"\n        )`,\n\t\t`INSERT INTO FullTypes VALUES(\"yyy\",\n            \"yyy\", \"yyy\",\n            false, false,\n            \"eHl6\", \"eHl6\",\n            \"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.123456789Z\",\n            200, 200,\n            0.5, 0.5,\n            \"2012-03-05\", \"2012-03-05\"\n        )`,\n\n\t\t`INSERT INTO ArrayTypes VALUES(100,\n           json_array(\"xxx1\", \"xxx2\"),\n           json_array(true, false),\n           json_array(\"eHl6\", \"eHl6\"),\n           json_array(\"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.999999999Z\"),\n           json_array(1, 2),\n           json_array(0.1, 0.2),\n           json_array(\"2012-03-04\", \"2012-03-05\")\n        )`,\n\n\t\t`INSERT INTO JoinA VALUES(100, \"xxx\")`,\n\t\t`INSERT INTO JoinA VALUES(200, \"yyy\")`,\n\t\t`INSERT INTO JoinA VALUES(300, \"zzz\")`,\n\n\t\t`INSERT INTO JoinB VALUES(100, \"aaa\")`,\n\t\t`INSERT INTO JoinB VALUES(200, \"bbb\")`,\n\t\t`INSERT INTO JoinB VALUES(400, \"ddd\")`,\n\n\t\t\"INSERT INTO `From` VALUES(1, 1, 1)\",\n\t} {\n\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t}\n\t}\n\n\ttable := map[string][]struct {\n\t\tname     string\n\t\tsql      string\n\t\tparams   map[string]Value\n\t\texpected [][]interface{}\n\t\tnames    []string\n\t\tcode     codes.Code\n\t\tmsg      *regexp.Regexp\n\t}{\n\t\t\"Simple\": {\n\t\t\t{\n\t\t\t\tname: \"NoTable_IntLiteral\",\n\t\t\t\tsql:  `SELECT 1`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"NoTable_StringLiteral\",\n\t\t\t\tsql:  `SELECT \"foo\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"foo\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"NoTable_NullLiteral\",\n\t\t\t\tsql:  `SELECT NULL`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{nil},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"NoTable_Params_Int\",\n\t\t\t\tsql:  `SELECT @foo`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(int64(100)),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"NoTable_Params_String\",\n\t\t\t\tsql:  `SELECT @foo`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Simple_Star\",\n\t\t\t\tsql:  \"SELECT * FROM Simple\",\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TableNotFound\",\n\t\t\t\tsql:  \"SELECT * FROM xxx\",\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`Table not found: xxx`),\n\t\t\t},\n\t\t\t// TODO: memefish cannot parse this sql\n\t\t\t// {\n\t\t\t// \tname: \"QueryParam_TableName\",\n\t\t\t// \tsql:  \"SELECT 1 FROM @table\",\n\t\t\t// \tcode: codes.InvalidArgument,\n\t\t\t// \tmsg:  regexp.MustCompile(`Query parameters cannot be used in place of table names`),\n\t\t\t// },\n\t\t\t{\n\t\t\t\tname: \"Star_WithoutFromClause\",\n\t\t\t\tsql:  \"SELECT *\",\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`SELECT \\* must have a FROM clause`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Simple_Identifer\",\n\t\t\t\tsql:  \"SELECT Value, Id FROM Simple\",\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"xxx\", int64(100)},\n\t\t\t\t\t[]interface{}{\"yyy\", int64(200)},\n\t\t\t\t\t[]interface{}{\"zzz\", int64(300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Identifer_NotFound\",\n\t\t\t\tsql:  \"SELECT x.* FROM Simple a\",\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`Unrecognized name: x`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Identifer_NotFound2\",\n\t\t\t\tsql:  \"SELECT foo FROM Simple\",\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`Unrecognized name: foo`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Identifer_NotFound3\",\n\t\t\t\tsql:  \"SELECT a.foo FROM Simple a\",\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(\"Name foo not found inside a\"),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Identifer_NotFound_WithoutFromClause\",\n\t\t\t\tsql:  \"SELECT x\",\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`Unrecognized name: x`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"PathIdentifer_InvalidFieldAccess\",\n\t\t\t\tsql:  \"SELECT a.Id.zzz FROM Simple a\",\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`Cannot access field zzz on a value with type INT64`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"PathIdentifer_NotFound_WithoutFromClause\",\n\t\t\t\tsql:  \"SELECT x.*\",\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`Unrecognized name: x`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Simple_Alias\",\n\t\t\t\tsql:  \"SELECT Id a, Value b FROM Simple\",\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Alias_DuplicateTableAlias\",\n\t\t\t\tsql:  \"SELECT 1 FROM Simple a, Simple a\",\n\t\t\t\t// code: codes.InvalidArgument,\n\t\t\t\tcode: codes.Unknown, // TODO\n\t\t\t\tmsg:  regexp.MustCompile(`Duplicate table alias a in the same FROM clause`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Alias_AmbiguousColumn\",\n\t\t\t\tsql:  \"SELECT Id FROM Simple a, Simple b\",\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`Column name Id is ambiguous`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ValueTypes_FullTypes\",\n\t\t\t\tsql:  `SELECT * FROM FullTypes`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t{\n\t\t\t\t\t\t\"xxx\", \"xxx\", \"xxx\",\n\t\t\t\t\t\ttrue, true,\n\t\t\t\t\t\t[]byte(\"eHl6\"), []byte(\"eHl6\"),\n\t\t\t\t\t\t\"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.123456789Z\",\n\t\t\t\t\t\tint64(100), int64(100),\n\t\t\t\t\t\tfloat64(0.5), float64(0.5),\n\t\t\t\t\t\t\"2012-03-04\", \"2012-03-04\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"yyy\", \"yyy\", \"yyy\",\n\t\t\t\t\t\tfalse, false,\n\t\t\t\t\t\t[]byte(\"eHl6\"), []byte(\"eHl6\"),\n\t\t\t\t\t\t\"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.123456789Z\",\n\t\t\t\t\t\tint64(200), int64(200),\n\t\t\t\t\t\tfloat64(0.5), float64(0.5),\n\t\t\t\t\t\t\"2012-03-05\", \"2012-03-05\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ValueTypes_ArrayTypes\",\n\t\t\t\tsql:  `SELECT * FROM ArrayTypes`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t{\n\t\t\t\t\t\tint64(100),\n\t\t\t\t\t\tmakeTestArray(TCString, \"xxx1\", \"xxx2\"),\n\t\t\t\t\t\tmakeTestArray(TCBool, true, false),\n\t\t\t\t\t\t[][]uint8{{0x78, 0x79, 0x7a}, {0x78, 0x79, 0x7a}},\n\t\t\t\t\t\tmakeTestArray(TCString, \"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.999999999Z\"),\n\t\t\t\t\t\tmakeTestArray(TCInt64, int64(1), int64(2)),\n\t\t\t\t\t\tmakeTestArray(TCFloat64, float64(0.1), float64(0.2)),\n\t\t\t\t\t\tmakeTestArray(TCString, \"2012-03-04\", \"2012-03-05\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"EscapeKeyword_Table\",\n\t\t\t\tsql:      \"SELECT * FROM `From`\",\n\t\t\t\tnames:    []string{\"ALL\", \"CAST\", \"JOIN\"},\n\t\t\t\texpected: [][]interface{}{{int64(1), int64(1), int64(1)}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"EscapeKeyword_TableAlias\",\n\t\t\t\tsql:      \"SELECT * FROM `From` AS `ALL`\",\n\t\t\t\tnames:    []string{\"ALL\", \"CAST\", \"JOIN\"},\n\t\t\t\texpected: [][]interface{}{{int64(1), int64(1), int64(1)}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"EscapeKeyword_Columns\",\n\t\t\t\tsql:      \"SELECT `ALL`, `CAST`, `JOIN` FROM `From`\",\n\t\t\t\tnames:    []string{\"ALL\", \"CAST\", \"JOIN\"},\n\t\t\t\texpected: [][]interface{}{{int64(1), int64(1), int64(1)}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"EscapeKeyword_ColumnsWithAlias\",\n\t\t\t\tsql:      \"SELECT `ALL`.`ALL`, `ALL`.`CAST`, `ALL`.`JOIN` FROM `From` AS `ALL`\",\n\t\t\t\tnames:    []string{\"ALL\", \"CAST\", \"JOIN\"},\n\t\t\t\texpected: [][]interface{}{{int64(1), int64(1), int64(1)}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"EscapeKeyword_ColumnAlias\",\n\t\t\t\tsql:      \"SELECT `ALL` AS `AND`, `CAST` `OR`, `JOIN` AS `IF` FROM `From`\",\n\t\t\t\tnames:    []string{\"AND\", \"OR\", \"IF\"},\n\t\t\t\texpected: [][]interface{}{{int64(1), int64(1), int64(1)}},\n\t\t\t},\n\t\t},\n\t\t\"SimpleWhere\": {\n\t\t\t{\n\t\t\t\tname: \"Simple_Where_IntLiteral\",\n\t\t\t\tsql:  \"SELECT Id, Value FROM Simple WHERE Id = 200\",\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Simple_Where_IntLiteral2\",\n\t\t\t\tsql:  \"SELECT Id, Value FROM Simple WHERE Id >= 200\",\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Simple_Where_IntLiteral3\",\n\t\t\t\tsql:  \"SELECT Id, Value FROM Simple WHERE Id = +200\",\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"Simple_Where_IntLiteral4\",\n\t\t\t\tsql:      \"SELECT Id, Value FROM Simple WHERE Id = -200\",\n\t\t\t\texpected: nil,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Simple_Where_StringLiteral\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE Value = \"xxx\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Simple_Where_StringLiteral2\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE Value > \"xxx\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Simple_Where_Param\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE Id = @id`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"id\": makeTestValue(100),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Simple_Where_AND\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE Id > @id AND Value = @val`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"id\":  makeTestValue(100),\n\t\t\t\t\t\"val\": makeTestValue(\"zzz\"),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Simple_Where_Paren\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE Id >= @id AND (Value = @val1 OR Value = @val2)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"id\":   makeTestValue(100),\n\t\t\t\t\t\"val1\": makeTestValue(\"zzz\"),\n\t\t\t\t\t\"val2\": makeTestValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"Simple_Where_LIKE\",\n\t\t\t\tsql:    `SELECT Id, Value FROM Simple WHERE Value LIKE \"x%\"`,\n\t\t\t\tparams: map[string]Value{},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"Simple_Where_NOT_LIKE\",\n\t\t\t\tsql:    `SELECT Id, Value FROM Simple WHERE Value NOT LIKE \"x%\"`,\n\t\t\t\tparams: map[string]Value{},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"Simple_Where_IN\",\n\t\t\t\tsql:    `SELECT Id, Value FROM Simple WHERE Id IN (100, 300)`,\n\t\t\t\tparams: map[string]Value{},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"Simple_Where_NOT_IN\",\n\t\t\t\tsql:    `SELECT Id, Value FROM Simple WHERE Id NOT IN (100, 300)`,\n\t\t\t\tparams: map[string]Value{},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"Simple_Where_IS_NULL\",\n\t\t\t\tsql:      `SELECT Id, Value FROM Simple WHERE Value IS NULL`,\n\t\t\t\tparams:   map[string]Value{},\n\t\t\t\texpected: nil,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"Simple_Where_IS_NOT_NULL\",\n\t\t\t\tsql:    `SELECT Id, Value FROM Simple WHERE Value IS NOT NULL`,\n\t\t\t\tparams: map[string]Value{},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"Simple_Where_IS_BOOL\",\n\t\t\t\tsql:      `SELECT Id, Value FROM Simple WHERE Id IS TRUE`,\n\t\t\t\tparams:   map[string]Value{},\n\t\t\t\texpected: nil,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"Simple_Where_IS_NOT_BOOL\",\n\t\t\t\tsql:    `SELECT Id, Value FROM Simple WHERE Id IS NOT TRUE`,\n\t\t\t\tparams: map[string]Value{},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"Simple_Where_BETWEEN\",\n\t\t\t\tsql:    `SELECT Id, Value FROM Simple WHERE Id BETWEEN 200 AND 300`,\n\t\t\t\tparams: map[string]Value{},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"Simple_Where_NOT_BETWEEN\",\n\t\t\t\tsql:    `SELECT Id, Value FROM Simple WHERE Id NOT BETWEEN 200 AND 300`,\n\t\t\t\tparams: map[string]Value{},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\n\t\t\t{\n\t\t\t\tname:   \"Simple_Where_STARTS_WITH\",\n\t\t\t\tsql:    `SELECT Id, Value FROM Simple WHERE STARTS_WITH(Value, \"x\")`,\n\t\t\t\tparams: map[string]Value{},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Simple_Where_STARTS_WITH_PARAM\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE STARTS_WITH(Value, @x)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"x\": makeTestValue(\"xx\"),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t\"SimpleGroup\": {\n\t\t\t{\n\t\t\t\tname:   \"Simple_GROUP\",\n\t\t\t\tsql:    `SELECT Id, Value FROM Simple GROUP BY Value`,\n\t\t\t\tparams: map[string]Value{},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"Simple_GROUP_HAVING\",\n\t\t\t\tsql:    `SELECT Id, Value FROM Simple GROUP BY Value HAVING Value > \"xxx\"`,\n\t\t\t\tparams: map[string]Value{},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"LimitOffset\": {\n\t\t\t{\n\t\t\t\tname:   \"Simple_LIMIT\",\n\t\t\t\tsql:    `SELECT Id, Value FROM Simple LIMIT 1`,\n\t\t\t\tparams: map[string]Value{},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Simple_LIMIT_Param\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple LIMIT @limit`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"limit\": makeTestValue(2),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"Simple_LIMIT_OFFSET\",\n\t\t\t\tsql:    `SELECT Id, Value FROM Simple LIMIT 1 OFFSET 1`,\n\t\t\t\tparams: map[string]Value{},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Simple_LIMIT_OFFSET_Param\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple LIMIT @limit OFFSET @offset`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"limit\":  makeTestValue(1),\n\t\t\t\t\t\"offset\": makeTestValue(1),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Limit_InvalidType\",\n\t\t\t\tsql:  `SELECT 1 FROM Simple LIMIT @foo`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(\"xx\"),\n\t\t\t\t},\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^LIMIT expects an integer literal or parameter`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Offset_InvalidType\",\n\t\t\t\tsql:  `SELECT 1 FROM Simple LIMIT 1 OFFSET @foo`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(\"xx\"),\n\t\t\t\t},\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^OFFSET expects an integer literal or parameter`),\n\t\t\t},\n\t\t},\n\n\t\t\"OrderBy\": {\n\t\t\t{\n\t\t\t\tname:   \"Simple_ORDER\",\n\t\t\t\tsql:    `SELECT Id, Value FROM Simple ORDER BY Id DESC`,\n\t\t\t\tparams: map[string]Value{},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"Simple_ORDER2\",\n\t\t\t\tsql:    `SELECT Id, Value FROM Simple ORDER BY Id DESC, Value`,\n\t\t\t\tparams: map[string]Value{},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"EscapeKeyword\",\n\t\t\t\tsql:      \"SELECT * FROM `From` ORDER BY `ALL`\",\n\t\t\t\tnames:    []string{\"ALL\", \"CAST\", \"JOIN\"},\n\t\t\t\texpected: [][]interface{}{{int64(1), int64(1), int64(1)}},\n\t\t\t},\n\t\t},\n\n\t\t\"Array\": {\n\t\t\t{\n\t\t\t\tname: \"ArrayIndex_OFFSET1\",\n\t\t\t\tsql:  `SELECT [1, 2, 3][OFFSET(0)]`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayIndex_OFFSET2\",\n\t\t\t\tsql:  `SELECT [1, 2, 3][OFFSET(2)]`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(3)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayIndex_OFFSET_Param\",\n\t\t\t\tsql:  `SELECT [1, 2, 3][OFFSET(@foo)]`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(1),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(2)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayIndex_ORDINAL1\",\n\t\t\t\tsql:  `SELECT [1, 2, 3][ORDINAL(1)]`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayIndex_ORDINAL3\",\n\t\t\t\tsql:  `SELECT [1, 2, 3][ORDINAL(3)]`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(3)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayIndex_ORDINAL_Param\",\n\t\t\t\tsql:  `SELECT [1, 2, 3][ORDINAL(@foo)]`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(1),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t},\n\t\t\t},\n\n\t\t\t{\n\t\t\t\tname: \"ArrayLiteral_Empty\",\n\t\t\t\tsql:  `SELECT []`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{makeTestArray(TCInt64)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayLiteral_IntLiteral\",\n\t\t\t\tsql:  `SELECT [1, 2, 3]`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{makeTestArray(TCInt64, 1, 2, 3)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayLiteral_Ident\",\n\t\t\t\tsql:  `SELECT [1, Id] FROM Simple`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{makeTestArray(TCInt64, 1, 100)},\n\t\t\t\t\t[]interface{}{makeTestArray(TCInt64, 1, 200)},\n\t\t\t\t\t[]interface{}{makeTestArray(TCInt64, 1, 300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayLiteral_Params\",\n\t\t\t\tsql:  `SELECT [\"xxx\", @foo]`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(\"yyy\"),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{makeTestArray(TCString, \"xxx\", \"yyy\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayLiteral_IncompatibleElements\",\n\t\t\t\tsql:  `SELECT [100, \"xxx\"]`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Array elements of types {.*} do not have a common supertype`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayLiteral_Unnest_ImcompatibleElements\",\n\t\t\t\tsql:  `SELECT * FROM UNNEST ([\"xxx\", 1])`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Array elements of types {.*} do not have a common supertype`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayLiteral_Unnest_Bool\",\n\t\t\t\tsql:  `SELECT 1 FROM UNNEST (true)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Values referenced in UNNEST must be arrays. UNNEST contains expression of type BOOL`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayLiteral_In_Unnest_Bool\",\n\t\t\t\tsql:  `SELECT 1 FROM Simple WHERE 1 IN UNNEST (true)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Second argument of IN UNNEST must be an array but was BOOL`),\n\t\t\t},\n\t\t\t// {\n\t\t\t// \tname: \"ArrayLiteral_IntAndFloat\",\n\t\t\t// \tsql: `SELECT [100, 0.1]`,\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{makeTestArray(TCFloat64, 0.1, 0.1)},\n\t\t\t// \t},\n\t\t\t// },\n\t\t\t{\n\t\t\t\tname: \"ArraySubquery_Simple\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT Id FROM Simple)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{makeTestArray(TCInt64, int64(100), int64(200), int64(300))},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArraySubquery_Simple_MultiColumns\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT Id, Id FROM Simple)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^ARRAY subquery cannot have more than one column unless using SELECT AS STRUCT to build STRUCT values`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArraySubquery_Star\",\n\t\t\t\tsql:  `SELECT x FROM (SELECT ARRAY(SELECT * FROM (SELECT Id FROM Simple)) x)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{makeTestArray(TCInt64, int64(100), int64(200), int64(300))},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArraySubquery_NestedArray\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT [1,2,3])`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Cannot use array subquery with column of type ARRAY<INT64> because nested arrays are not supported`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayLiteral_NestedArray\",\n\t\t\t\tsql:  `SELECT [(SELECT [1,2,3])]`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Cannot construct array with element type ARRAY<INT64> because nested arrays are not supported`),\n\t\t\t},\n\t\t},\n\t\t\"Timestamp\": {\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_Date\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T08:00:00Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_Date2\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-1-2\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T08:00:00Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_WithoutNano\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02 12:34:56\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T20:34:56Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_WithoutNano2\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02 1:2:3\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T09:02:03Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_WithoutNano_T\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02T12:34:56\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T20:34:56Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_WithNano\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02 1:2:3.123456789\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T09:02:03.123456789Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_WithNano2\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02 01:02:03.123456\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T09:02:03.123456Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_WithNano3\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02 01:02:03.12\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T09:02:03.12Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_WithNano_T\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02T1:2:3.123456789\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T09:02:03.123456789Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_Timezone\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02 12:02:03Z\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T12:02:03Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_Timezone2\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02 12:02:03+01\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T11:02:03Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_Timezone3\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02 12:02:03-01\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T13:02:03Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_Timezone4\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02 12:02:03+01:30\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T10:32:03Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_Timezone_T\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02T12:02:03Z\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T12:02:03Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_WithNano_Timezone\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02 12:02:03.123456789Z\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T12:02:03.123456789Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_WithNano_Timezone2\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02 12:02:03.123456789+03\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T09:02:03.123456789Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"TimestampLiteral_WithNano_Timezone_T\",\n\t\t\t\tsql:  `SELECT TIMESTAMP \"1999-01-02T12:02:03.123456789Z\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T12:02:03.123456789Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"DateLiteral\",\n\t\t\t\tsql:  `SELECT DATE \"1999-01-02\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"DateLiteral2\",\n\t\t\t\tsql:  `SELECT DATE \"1999-1-2\"`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"BytesLiteral\",\n\t\t\t\tsql:  `SELECT B'xyz'`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{[]byte(\"xyz\")},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t\"InUnnest\": {\n\t\t\t{\n\t\t\t\tname: \"Bind\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE Id IN UNNEST (@foo)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue([]int64{100, 200}),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Bind2\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE Id IN UNNEST (@foo)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue([]int64{}),\n\t\t\t\t},\n\t\t\t\texpected: nil,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Bind3\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE Value IN UNNEST (@foo)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue([]string{\"yyy\"}),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayLiteral\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE Id IN UNNEST ([100 ,200, 300])`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayLiteral2\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE Value IN UNNEST ([\"xxx\"])`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Ident\",\n\t\t\t\tsql:  `SELECT Id FROM ArrayTypes WHERE 1 IN UNNEST (ArrayInt)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"Ident2\",\n\t\t\t\tsql:      `SELECT Id FROM ArrayTypes WHERE 1 NOT IN UNNEST (ArrayInt)`,\n\t\t\t\texpected: nil,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Path\",\n\t\t\t\tsql:  `SELECT Id FROM ArrayTypes a WHERE 1 IN UNNEST (a.ArrayInt)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayStructLiteral_Named\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE (Id, Value) IN UNNEST(ARRAY<STRUCT<x INT64, y STRING>>[(100, \"xxx\"), (200, \"yyy\")])`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ArrayStructLiteral_Unnamed\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE (Id, Value) IN UNNEST(ARRAY<STRUCT<INT64, STRING>>[(100, \"xxx\"), (200, \"yyy\")])`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"FromUnnest\": {\n\t\t\t{\n\t\t\t\tname: \"Lietral_String\",\n\t\t\t\tsql:  `SELECT * FROM UNNEST ([\"xxx\", \"yyy\"])`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"xxx\"},\n\t\t\t\t\t[]interface{}{\"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Literal_Int\",\n\t\t\t\tsql:  `SELECT * FROM UNNEST ([1,2,3])`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t\t[]interface{}{int64(2)},\n\t\t\t\t\t[]interface{}{int64(3)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Literal_Params\",\n\t\t\t\tsql:  `SELECT * FROM UNNEST ([ @a, @b, @c])`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"a\": makeTestValue(1),\n\t\t\t\t\t\"b\": makeTestValue(2),\n\t\t\t\t\t\"c\": makeTestValue(3),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t\t[]interface{}{int64(2)},\n\t\t\t\t\t[]interface{}{int64(3)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Literal_Params_Array\",\n\t\t\t\tsql:  `SELECT * FROM UNNEST (@foo)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue([]int64{3, 4}),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(3)},\n\t\t\t\t\t[]interface{}{int64(4)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Literal_Params_Array_WithOffset\",\n\t\t\t\tsql:  `SELECT * FROM UNNEST (@foo) WITH OFFSET`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue([]int64{3, 4}),\n\t\t\t\t},\n\t\t\t\tnames: []string{\"\", \"\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(3), int64(0)},\n\t\t\t\t\t[]interface{}{int64(4), int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Literal_Params_Alias\",\n\t\t\t\tsql:  `SELECT x FROM UNNEST (@foo) AS x`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue([]int64{3, 4}),\n\t\t\t\t},\n\t\t\t\tnames: []string{\"x\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(3)},\n\t\t\t\t\t[]interface{}{int64(4)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Literal_Params_WithOffset_Alias\",\n\t\t\t\tsql:  `SELECT x, y FROM UNNEST (@foo) AS x WITH OFFSET y`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue([]int64{3, 4}),\n\t\t\t\t},\n\t\t\t\tnames: []string{\"x\", \"y\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(3), int64(0)},\n\t\t\t\t\t[]interface{}{int64(4), int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"Literal_Alias_Star\",\n\t\t\t\tsql:   `SELECT * FROM UNNEST ([1,2,3]) AS xxx`,\n\t\t\t\tnames: []string{\"xxx\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t\t[]interface{}{int64(2)},\n\t\t\t\t\t[]interface{}{int64(3)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"Literal_Alias_Star_WithOffset\",\n\t\t\t\tsql:   `SELECT * FROM UNNEST ([1,2,3]) AS xxx WITH OFFSET AS yyy`,\n\t\t\t\tnames: []string{\"xxx\", \"yyy\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1), int64(0)},\n\t\t\t\t\t[]interface{}{int64(2), int64(1)},\n\t\t\t\t\t[]interface{}{int64(3), int64(2)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"Literal_Alias_Ident\",\n\t\t\t\tsql:   `SELECT xxx FROM UNNEST ([1,2,3]) AS xxx`,\n\t\t\t\tnames: []string{\"xxx\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t\t[]interface{}{int64(2)},\n\t\t\t\t\t[]interface{}{int64(3)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"Literal_Alias_WithOffset_Ident\",\n\t\t\t\tsql:   `SELECT xxx, yyy FROM UNNEST ([1,2,3]) AS xxx WITH OFFSET AS yyy`,\n\t\t\t\tnames: []string{\"xxx\", \"yyy\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1), int64(0)},\n\t\t\t\t\t[]interface{}{int64(2), int64(1)},\n\t\t\t\t\t[]interface{}{int64(3), int64(2)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// TODO: To be able to use Ident and Path in FROM clause\n\t\t\t// {\n\t\t\t// \tname: \"Join\",\n\t\t\t// \tsql:  `SELECT Id, flatten FROM ArrayTypes, UNNEST (ArrayTypes.ArrayString) AS flatten`,\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{int64(1), int64(0)},\n\t\t\t// \t\t[]interface{}{int64(1), int64(1)},\n\t\t\t// \t},\n\t\t\t// },\n\t\t\t// {\n\t\t\t// \tname: \"Join2\",\n\t\t\t// \tsql:  `SELECT Id, flatten FROM ArrayTypes, UNNEST (ArrayString) AS flatten`,\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{int64(1), int64(0)},\n\t\t\t// \t\t[]interface{}{int64(1), int64(1)},\n\t\t\t// \t},\n\t\t\t// },\n\t\t\t// {\n\t\t\t// \tname: \"Join3\",\n\t\t\t// \tsql:  `SELECT a.Id, flatten FROM ArrayTypes a, UNNEST (a.ArrayString) AS flatten`,\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{int64(1), int64(0)},\n\t\t\t// \t\t[]interface{}{int64(1), int64(1)},\n\t\t\t// \t},\n\t\t\t// },\n\t\t\t{\n\t\t\t\tname:  \"SubQuery_Array\",\n\t\t\t\tsql:   `SELECT * FROM UNNEST(ARRAY(SELECT Id FROM Simple))`,\n\t\t\t\tnames: []string{\"\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"SubQuery_Array_WithoutColumnAlias\",\n\t\t\t\tsql:   `SELECT * FROM UNNEST(ARRAY(SELECT Id+1 FROM Simple))`,\n\t\t\t\tnames: []string{\"\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(101)},\n\t\t\t\t\t[]interface{}{int64(201)},\n\t\t\t\t\t[]interface{}{int64(301)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"SubQuery_Array_WithColumnAlias\",\n\t\t\t\tsql:   `SELECT * FROM UNNEST(ARRAY(SELECT Id As x FROM Simple))`,\n\t\t\t\tnames: []string{\"\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"SubQuery_Array_AsStruct\",\n\t\t\t\tsql:   `SELECT * FROM UNNEST(ARRAY(SELECT AS STRUCT Id, Value FROM Simple))`,\n\t\t\t\tnames: []string{\"Id\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"Literal_Struct_Star\",\n\t\t\t\tsql:   `SELECT * FROM UNNEST(ARRAY<STRUCT<x INT64, y STRING>>[(1, 'foo'), (3, 'bar')])`,\n\t\t\t\tnames: []string{\"x\", \"y\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1), \"foo\"},\n\t\t\t\t\t[]interface{}{int64(3), \"bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"Literal_Struct_WithOffset_Star\",\n\t\t\t\tsql:   `SELECT * FROM UNNEST(ARRAY<STRUCT<x INT64, y STRING>>[(1, 'foo'), (3, 'bar')]) WITH OFFSET`,\n\t\t\t\tnames: []string{\"x\", \"y\", \"\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1), \"foo\", int64(0)},\n\t\t\t\t\t[]interface{}{int64(3), \"bar\", int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"Literal_Struct_WithOffsetAlias_Star\",\n\t\t\t\tsql:   `SELECT * FROM UNNEST(ARRAY<STRUCT<x INT64, y STRING>>[(1, 'foo'), (3, 'bar')]) WITH OFFSET AS offset`,\n\t\t\t\tnames: []string{\"x\", \"y\", \"offset\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1), \"foo\", int64(0)},\n\t\t\t\t\t[]interface{}{int64(3), \"bar\", int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"Literal_Struct_WithOffsetAlias_Identifer\",\n\t\t\t\tsql:   `SELECT x, y, offset FROM UNNEST(ARRAY<STRUCT<x INT64, y STRING>>[(1, 'foo'), (3, 'bar')]) WITH OFFSET AS offset`,\n\t\t\t\tnames: []string{\"x\", \"y\", \"offset\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1), \"foo\", int64(0)},\n\t\t\t\t\t[]interface{}{int64(3), \"bar\", int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"Literal_Struct_Alias_Star\",\n\t\t\t\tsql:   `SELECT * FROM UNNEST(ARRAY<STRUCT<x INT64, y STRING>>[(1, 'foo'), (3, 'bar')]) AS foo WITH OFFSET`,\n\t\t\t\tnames: []string{\"x\", \"y\", \"\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1), \"foo\", int64(0)},\n\t\t\t\t\t[]interface{}{int64(3), \"bar\", int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// {\n\t\t\t// \tname:  \"Literal_Struct_Alias_DotStar\",\n\t\t\t// \tsql:   `SELECT foo.* FROM UNNEST(ARRAY<STRUCT<x INT64, y STRING>>[(1, 'foo'), (3, 'bar')]) AS foo WITH OFFSET`,\n\t\t\t// \tnames: []string{\"x\", \"y\"},\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{int64(1), \"foo\"},\n\t\t\t// \t\t[]interface{}{int64(3), \"bar\"},\n\t\t\t// \t},\n\t\t\t// },\n\t\t\t// {\n\t\t\t// \tname:  \"Literal_Struct_Alias_Identifer\",\n\t\t\t// \tsql:   `SELECT foo.x, foo.y, offset FROM UNNEST(ARRAY<STRUCT<x INT64, y STRING>>[(1, 'foo'), (3, 'bar')]) WITH OFFSET AS offset`,\n\t\t\t// \tnames: []string{\"x\", \"y\", \"offset\"},\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{int64(1), \"foo\", int64(0)},\n\t\t\t// \t\t[]interface{}{int64(3), \"bar\", int64(1)},\n\t\t\t// \t},\n\t\t\t// },\n\t\t},\n\n\t\t\"Struct\": {\n\t\t\t{\n\t\t\t\tname: \"StructLiteral\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT (1,\"xx\") x)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"\", \"\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(1), string(\"xx\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"StructLiteral_WithField\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT STRUCT<Id int64, Value string>(1,\"xx\") x)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(1), string(\"xx\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"StructLiteral_WithField_WithoutName\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT STRUCT<int64, Value string>(1,\"xx\") x)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(1), string(\"xx\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"StructLiteral_WithArrayLiteral_OK\",\n\t\t\t\tsql:  `SELECT [(SELECT STRUCT<Id int64, Value string>(1,\"xx\"))]`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(1), string(\"xx\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// TODO: this should be error in spanner\n\t\t\t{\n\t\t\t\tname: \"StructLiteral_WithArrayLiteral_NG\",\n\t\t\t\tsql:  `SELECT [STRUCT<Id int64, Value string>(1,\"xx\")]`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(1), string(\"xx\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// Parse error\n\t\t\t// {\n\t\t\t// \tname: \"StructLiteral_WithField_WithoutType\",\n\t\t\t// \tsql:  `SELECT ARRAY(SELECT STRUCT<Id, Value string>(1,\"xx\") x)`,\n\t\t\t// },\n\t\t\t{\n\t\t\t\tname: \"StructLiteral_InvalidFieldSize\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT STRUCT<Id INT64>(1,\"xx\") x)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^STRUCT type has 1 fields but constructor call has 2 fields`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"StructLiteral_InvalidFieldSize\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT STRUCT<>(1,\"xx\") x)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^STRUCT type has 0 fields but constructor call has 2 fields`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"StructLiteral_IncompatibleType\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT STRUCT<Id INT64, Value INT64>(1,\"xx\") x)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Struct field 2 has type literal STRING which does not coerce to INT64`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"PathIdentifier_StructField_BeginFromTableAlias\",\n\t\t\t\tsql:  `SELECT y.x.Id, y.x.Value FROM (SELECT STRUCT<Id int64, Value string>(1, \"xx\") x) y`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1), string(\"xx\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"PathIdentifier_StructField_BeginFromStructColumnAlias\",\n\t\t\t\tsql:  `SELECT x.Id, x.Value FROM (SELECT STRUCT<Id int64, Value string>(1, \"xx\") x)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1), string(\"xx\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"PathIdentifier_StructField_Subquery_NotFound\",\n\t\t\t\tsql:  `SELECT y.x.zzz FROM (SELECT STRUCT<Id int64, Value string>(1, \"xx\") x) y`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Field name zzz does not exist in STRUCT<Id INT64, Value STRING>`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Selector_ArrayOfStruct\",\n\t\t\t\tsql:  `SELECT z.y[OFFSET(0)].Id FROM (SELECT ARRAY(SELECT STRUCT<Id int64, Value string>(1,\"xx\") x) y) z`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"DotStar_Ident_StructField\",\n\t\t\t\tsql:   `SELECT x.* FROM (SELECT STRUCT<Id int64, Value string>(1, \"xx\") x)`,\n\t\t\t\tnames: []string{\"Id\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1), string(\"xx\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"DotStar_Path_StructField\",\n\t\t\t\tsql:   `SELECT y.x.* FROM (SELECT STRUCT<Id int64, Value string>(1, \"xx\") x) y`,\n\t\t\t\tnames: []string{\"Id\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1), string(\"xx\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"SelectAsStruct\",\n\t\t\t\tsql:   `SELECT ARRAY(SELECT AS STRUCT Id, Value FROM Simple)`,\n\t\t\t\tnames: []string{\"\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(100), string(\"xxx\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(200), string(\"yyy\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(300), string(\"zzz\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"SelectAsStruct_Star\",\n\t\t\t\tsql:   `SELECT ARRAY(SELECT AS STRUCT * FROM Simple)`,\n\t\t\t\tnames: []string{\"\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(100), string(\"xxx\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(200), string(\"yyy\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(300), string(\"zzz\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"SelectAsStruct_Star_SubQuery\",\n\t\t\t\tsql:   `SELECT ARRAY(SELECT AS STRUCT * FROM (SELECT Id+1, CONCAT(Value, \"a\") FROM Simple))`,\n\t\t\t\tnames: []string{\"\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"\", \"\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(101), string(\"xxxa\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"\", \"\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(201), string(\"yyya\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"\", \"\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(301), string(\"zzza\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"SelectAsStruct_DotStar\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT AS STRUCT a.* FROM Simple a)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(100), string(\"xxx\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(200), string(\"yyy\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(300), string(\"zzz\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"SelectAsStruct_NoColumnName\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT AS STRUCT Id+1, Value FROM Simple)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(101), string(\"xxx\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(201), string(\"yyy\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(301), string(\"zzz\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"SelectAsStruct_WithColumnAlias\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT AS STRUCT Id+1 AS XX, Value FROM Simple)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"XX\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(101), string(\"xxx\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"XX\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(201), string(\"yyy\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys:   []string{\"XX\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{int64(301), string(\"zzz\")},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ValueTypes_FullTypes\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT AS STRUCT * FROM FullTypes)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys: fullTypesKeys,\n\t\t\t\t\t\t\t\tValues: []interface{}{\n\t\t\t\t\t\t\t\t\t\"xxx\", \"xxx\", \"xxx\",\n\t\t\t\t\t\t\t\t\ttrue, true,\n\t\t\t\t\t\t\t\t\t[]byte(\"xyz\"), []byte(\"xyz\"),\n\t\t\t\t\t\t\t\t\t\"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.123456789Z\",\n\t\t\t\t\t\t\t\t\tint64(100), int64(100),\n\t\t\t\t\t\t\t\t\tfloat64(0.5), float64(0.5),\n\t\t\t\t\t\t\t\t\t\"2012-03-04\", \"2012-03-04\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys: fullTypesKeys,\n\t\t\t\t\t\t\t\tValues: []interface{}{\n\t\t\t\t\t\t\t\t\t\"yyy\", \"yyy\", \"yyy\",\n\t\t\t\t\t\t\t\t\tfalse, false,\n\t\t\t\t\t\t\t\t\t[]byte(\"xyz\"), []byte(\"xyz\"),\n\t\t\t\t\t\t\t\t\t\"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.123456789Z\",\n\t\t\t\t\t\t\t\t\tint64(200), int64(200),\n\t\t\t\t\t\t\t\t\tfloat64(0.5), float64(0.5),\n\t\t\t\t\t\t\t\t\t\"2012-03-05\", \"2012-03-05\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ValueTypes_ArrayTypes\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT AS STRUCT * FROM ArrayTypes)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys: arrayTypesKeys,\n\t\t\t\t\t\t\t\tValues: []interface{}{\n\t\t\t\t\t\t\t\t\tint64(100),\n\t\t\t\t\t\t\t\t\tmakeTestArray(TCString, \"xxx1\", \"xxx2\"),\n\t\t\t\t\t\t\t\t\tmakeTestArray(TCBool, true, false),\n\t\t\t\t\t\t\t\t\t[][]uint8{{0x78, 0x79, 0x7a}, {0x78, 0x79, 0x7a}},\n\t\t\t\t\t\t\t\t\tmakeTestArray(TCString, \"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.999999999Z\"),\n\t\t\t\t\t\t\t\t\tmakeTestArray(TCInt64, int64(1), int64(2)),\n\t\t\t\t\t\t\t\t\tmakeTestArray(TCFloat64, float64(0.1), float64(0.2)),\n\t\t\t\t\t\t\t\t\tmakeTestArray(TCString, \"2012-03-04\", \"2012-03-05\"),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ValueTypes_ArrayTypes\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT AS STRUCT * FROM ArrayTypes)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys: arrayTypesKeys,\n\t\t\t\t\t\t\t\tValues: []interface{}{\n\t\t\t\t\t\t\t\t\tint64(100),\n\t\t\t\t\t\t\t\t\tmakeTestArray(TCString, \"xxx1\", \"xxx2\"),\n\t\t\t\t\t\t\t\t\tmakeTestArray(TCBool, true, false),\n\t\t\t\t\t\t\t\t\t[][]uint8{{0x78, 0x79, 0x7a}, {0x78, 0x79, 0x7a}},\n\t\t\t\t\t\t\t\t\tmakeTestArray(TCString, \"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.999999999Z\"),\n\t\t\t\t\t\t\t\t\tmakeTestArray(TCInt64, int64(1), int64(2)),\n\t\t\t\t\t\t\t\t\tmakeTestArray(TCFloat64, float64(0.1), float64(0.2)),\n\t\t\t\t\t\t\t\t\tmakeTestArray(TCString, \"2012-03-04\", \"2012-03-05\"),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ValueTypes_NestedArrayOfStruct\",\n\t\t\t\tsql:  `SELECT ARRAY(SELECT AS STRUCT ARRAY(SELECT AS STRUCT * FROM ArrayTypes) xx) yy`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKeys: []string{\"xx\"},\n\t\t\t\t\t\t\t\tValues: []interface{}{\n\t\t\t\t\t\t\t\t\t[]*StructValue{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tKeys: arrayTypesKeys,\n\t\t\t\t\t\t\t\t\t\t\tValues: []interface{}{\n\t\t\t\t\t\t\t\t\t\t\t\tint64(100),\n\t\t\t\t\t\t\t\t\t\t\t\tmakeTestArray(TCString, \"xxx1\", \"xxx2\"),\n\t\t\t\t\t\t\t\t\t\t\t\tmakeTestArray(TCBool, true, false),\n\t\t\t\t\t\t\t\t\t\t\t\t[][]uint8{{0x78, 0x79, 0x7a}, {0x78, 0x79, 0x7a}},\n\t\t\t\t\t\t\t\t\t\t\t\tmakeTestArray(TCString, \"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.999999999Z\"),\n\t\t\t\t\t\t\t\t\t\t\t\tmakeTestArray(TCInt64, int64(1), int64(2)),\n\t\t\t\t\t\t\t\t\t\t\t\tmakeTestArray(TCFloat64, float64(0.1), float64(0.2)),\n\t\t\t\t\t\t\t\t\t\t\t\tmakeTestArray(TCString, \"2012-03-04\", \"2012-03-05\"),\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"StructOfStruct\",\n\t\t\t\tsql:  `SELECT [(SELECT STRUCT<Id INT64, STRUCT<Id INT64>>(1, (SELECT AS STRUCT Id FROM Simple)))]`,\n\t\t\t\tcode: codes.Unimplemented,\n\t\t\t\tmsg:  regexp.MustCompile(`^Unsupported query shape: A struct value cannot be returned as a column value. Rewrite the query to flatten the struct fields in the result.`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Where_Compare_Untyped\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE (Id, Value) = (100, \"xxx\")`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Where_Compare_TypedWithName\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE (Id, Value) = STRUCT<x INT64, y STRING>(100, \"xxx\")`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Where_Compare_TypedWithName2\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE STRUCT<x INT64, y STRING>(Id, Value) = STRUCT<x INT64, y STRING>(100, \"xxx\")`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Where_Compare_TypedWithoutName\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE (Id, Value) = STRUCT<INT64, STRING>(100, \"xxx\")`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Where_In_Untyped\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE (Id, Value) IN ((100, \"xxx\"), (200, \"yyy\"))`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Where_In_TypedWithName\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE STRUCT<x INT64, y STRING>(Id, Value) IN ((100, \"xxx\"), (200, \"yyy\"))`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Where_In_TypedWithName2\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE (Id, Value) IN (STRUCT<x INT64, y STRING>(100, \"xxx\"), STRUCT<x INT64, y STRING>(200, \"yyy\"))`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Where_NotIn\",\n\t\t\t\tsql:  `SELECT Id, Value FROM Simple WHERE (Id, Value) NOT IN ((100, \"xxx\"), (200, \"yyy\"))`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t\"FromJoin\": {\n\t\t\t{\n\t\t\t\tname:  \"InnerJoin\",\n\t\t\t\tsql:   `SELECT a.*, b.* FROM JoinA a INNER JOIN JoinB b USING (Id)`,\n\t\t\t\tnames: []string{\"Id\", \"Value\", \"Id\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\", int64(100), \"aaa\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\", int64(200), \"bbb\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"CrossJoin\",\n\t\t\t\tsql:   `SELECT a.*, b.* FROM JoinA a CROSS JOIN JoinB b`,\n\t\t\t\tnames: []string{\"Id\", \"Value\", \"Id\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\", int64(100), \"aaa\"},\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\", int64(200), \"bbb\"},\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\", int64(400), \"ddd\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\", int64(100), \"aaa\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\", int64(200), \"bbb\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\", int64(400), \"ddd\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\", int64(100), \"aaa\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\", int64(200), \"bbb\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\", int64(400), \"ddd\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"CommaJoin\",\n\t\t\t\tsql:   `SELECT a.*, b.* FROM JoinA a, JoinB b`,\n\t\t\t\tnames: []string{\"Id\", \"Value\", \"Id\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\", int64(100), \"aaa\"},\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\", int64(200), \"bbb\"},\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\", int64(400), \"ddd\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\", int64(100), \"aaa\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\", int64(200), \"bbb\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\", int64(400), \"ddd\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\", int64(100), \"aaa\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\", int64(200), \"bbb\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\", int64(400), \"ddd\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"LeftOuterJoin\",\n\t\t\t\tsql:   `SELECT a.*, b.* FROM JoinA a LEFT OUTER JOIN JoinB b USING (Id)`,\n\t\t\t\tnames: []string{\"Id\", \"Value\", \"Id\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\", int64(100), \"aaa\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\", int64(200), \"bbb\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\", nil, nil},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"RightOuterJoin\",\n\t\t\t\tsql:   `SELECT a.*, b.* FROM JoinA a Right OUTER JOIN JoinB b USING (Id)`,\n\t\t\t\tnames: []string{\"Id\", \"Value\", \"Id\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\", int64(100), \"aaa\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\", int64(200), \"bbb\"},\n\t\t\t\t\t[]interface{}{nil, nil, int64(400), \"ddd\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"FullOuterJoin\",\n\t\t\t\tsql:   `SELECT * FROM JoinA a FULL OUTER JOIN JoinB b USING (Id)`,\n\t\t\t\tnames: []string{\"Id\", \"Value\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\", \"aaa\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\", \"bbb\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\", nil},\n\t\t\t\t\t[]interface{}{int64(400), nil, \"ddd\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"FullOuterJoin_UsingMultiIdentifer\",\n\t\t\t\tsql:   `SELECT * FROM JoinA a FULL OUTER JOIN JoinB b USING (Id, Value)`,\n\t\t\t\tnames: []string{\"Id\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t\t[]interface{}{int64(100), \"aaa\"},\n\t\t\t\t\t[]interface{}{int64(200), \"bbb\"},\n\t\t\t\t\t[]interface{}{int64(400), \"ddd\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// TODO: FullOuterJoin is simulated only for USING\n\t\t\t// {\n\t\t\t// \tname:  \"FullOuterJoin_On\",\n\t\t\t// \tsql:   `SELECT * FROM JoinA a FULL OUTER JOIN JoinB b ON a.Id = b.Id`,\n\t\t\t// \tnames: []string{\"Id\", \"Value\", \"Id\", \"Value\"},\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{int64(100), \"xxx\", int64(100), \"aaa\"},\n\t\t\t// \t\t[]interface{}{int64(200), \"yyy\", int64(200), \"bbb\"},\n\t\t\t// \t\t[]interface{}{int64(300), \"zzz\", nil, nil},\n\t\t\t// \t\t[]interface{}{nil, nil, int64(400), \"ddd\"},\n\t\t\t// \t},\n\t\t\t// },\n\t\t\t// TODO: FullOuterJoin is simulated by using subquery with UNION, so table alias cannot be used\n\t\t\t// {\n\t\t\t// \tname:  \"FullOuterJoin_WithColumnNames\",\n\t\t\t// \tsql:   `SELECT a.Id, a.Value, b.Value FROM JoinA a FULL OUTER JOIN JoinB b USING (Id)`,\n\t\t\t// \tnames: []string{\"Id\", \"Value\", \"Value\"},\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{int64(100), \"xxx\", \"aaa\"},\n\t\t\t// \t\t[]interface{}{int64(200), \"yyy\", \"bbb\"},\n\t\t\t// \t\t[]interface{}{int64(300), \"zzz\", nil},\n\t\t\t// \t\t[]interface{}{int64(400), nil, \"ddd\"},\n\t\t\t// \t},\n\t\t\t// },\n\t\t\t// {\n\t\t\t// \tname:  \"FullOuterJoin_WithTableAlias\",\n\t\t\t// \tsql:   `SELECT a.*, b.* FROM JoinA a FULL OUTER JOIN JoinB b USING (Id)`,\n\t\t\t// \tnames: []string{\"Id\", \"Value\"},\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{int64(100), \"xxx\", int64(100), \"aaa\"},\n\t\t\t// \t\t[]interface{}{int64(200), \"yyy\", int64(200), \"bbb\"},\n\t\t\t// \t\t[]interface{}{int64(300), \"zzz\", nil, nil},\n\t\t\t// \t\t[]interface{}{nil, nil, int64(400), \"ddd\"},\n\t\t\t// \t},\n\t\t\t// },\n\t\t\t{\n\t\t\t\tname:  \"Cond_ON\",\n\t\t\t\tsql:   `SELECT a.* FROM JoinA AS a JOIN JoinB AS b ON a.Id = b.Id WHERE b.Value = \"aaa\"`,\n\t\t\t\tnames: []string{\"Id\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"Cond_ON1\",\n\t\t\t\tsql:   `SELECT a.Id, a.Value FROM JoinA AS a JOIN JoinB AS b ON a.Id = b.Id WHERE b.Value = \"aaa\"`,\n\t\t\t\tnames: []string{\"Id\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cond_ON2\",\n\t\t\t\tsql:  `SELECT * FROM JoinA AS a JOIN JoinB AS b ON a.Id = b.Id WHERE a.Id = @id`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"id\": makeTestValue(200),\n\t\t\t\t},\n\t\t\t\tnames: []string{\"Id\", \"Value\", \"Id\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\", int64(200), \"bbb\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cond_ON3\",\n\t\t\t\tsql:  `SELECT * FROM JoinA AS a JOIN JoinB AS b ON a.Id = b.Id WHERE a.Id = @id`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"id\": makeTestValue(200),\n\t\t\t\t},\n\t\t\t\tnames: []string{\"Id\", \"Value\", \"Id\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\", int64(200), \"bbb\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"Cond_Using_SingleIdentifier\",\n\t\t\t\tsql:   `SELECT * FROM JoinA AS a JOIN JoinB AS b USING (Id) WHERE b.Value = \"aaa\"`,\n\t\t\t\tnames: []string{\"Id\", \"Value\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\", \"aaa\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"Cond_Using_MultiIdentifer\",\n\t\t\t\tsql:   `SELECT * FROM JoinA AS a JOIN Simple AS b USING (Id, Value)`,\n\t\t\t\tnames: []string{\"Id\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cond_Using_ColumnNotExist\",\n\t\t\t\tsql:  `SELECT 1 FROM JoinA a JOIN JoinB b USING (foo)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Column foo in USING clause not found on left side of join`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cond_Using_ColumnNotExist_RightSide\",\n\t\t\t\tsql:  `SELECT 1 FROM JoinA a JOIN CompositePrimaryKeys b USING (Value)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Column Value in USING clause not found on right side of join`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cond_Using_ColumnNotExist_Subquery\",\n\t\t\t\tsql:  `SELECT 1 FROM JoinA a JOIN (SELECT Value FROM JoinB) b USING (Id)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Column Id in USING clause not found on right side of join`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"Cond_USING_EscapeKeyword\",\n\t\t\t\tsql:      \"SELECT `AND`.*, `OR`.* FROM `From` `AND` JOIN `From` `OR` USING (`ALL`)\",\n\t\t\t\tnames:    []string{\"ALL\", \"CAST\", \"JOIN\", \"ALL\", \"CAST\", \"JOIN\"},\n\t\t\t\texpected: [][]interface{}{{int64(1), int64(1), int64(1), int64(1), int64(1), int64(1)}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"Subquery_USING\",\n\t\t\t\tsql:   `SELECT a.* FROM JoinA AS a JOIN (SELECT Id FROM JoinB) AS b USING (Id) WHERE a.Value = \"xxx\"`,\n\t\t\t\tnames: []string{\"Id\", \"Value\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"From_Join_Paren\",\n\t\t\t\tsql:   `SELECT a.Id FROM (JoinA AS a JOIN JoinB AS b USING (Id))`,\n\t\t\t\tnames: []string{\"Id\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"From_Subquery_Simple\",\n\t\t\t\tsql:   `SELECT s.* FROM (SELECT 1) s`,\n\t\t\t\tnames: []string{\"\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"From_Subquery_Table\",\n\t\t\t\tsql:   `SELECT s.* FROM (SELECT Id FROM Simple WHERE Value = \"xxx\") s`,\n\t\t\t\tnames: []string{\"Id\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t\"Subquery\": {\n\t\t\t{\n\t\t\t\tname: \"SubQuery_Scalar_Simple\",\n\t\t\t\tsql:  `SELECT Id FROM Simple WHERE 1 = (SELECT 1)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Scalar_WithMultiColumns\",\n\t\t\t\tsql:  `SELECT 1 WHERE 1 = (SELECT 1, \"abc\")`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Scalar subquery cannot have more than one column unless using SELECT AS STRUCT to build STRUCT values`),\n\t\t\t},\n\t\t\t// TODO: sqlite implicitly extract 1 row if subquery returns multi rows\n\t\t\t// {\n\t\t\t// \tname: \"Scalar_ReturnedMultiRows\",\n\t\t\t// \tsql:  \"SELECT 1 FROM Simple WHERE 1 = (SELECT 1 FROM Simple)\",\n\t\t\t// \tcode: codes.InvalidArgument,\n\t\t\t// \tmsg:  regexp.MustCompile(`A scalar subquery returned more than one row.`),\n\t\t\t// },\n\t\t\t{\n\t\t\t\tname: \"SubQuery_Scalar_Table\",\n\t\t\t\tsql:  `SELECT Id FROM Simple WHERE Id = (SELECT Id From Simple WHERE Id = 100)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"SubQuery_In\",\n\t\t\t\tsql:  `SELECT Id FROM Simple WHERE Id IN (SELECT Id From Simple WHERE Id > 100)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"SubQuery_NotIn\",\n\t\t\t\tsql:  `SELECT Id FROM Simple WHERE Id NOT IN (SELECT Id From Simple WHERE Id > 100)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"SubQuery_In_WithMultiColumns\",\n\t\t\t\tsql:  `SELECT 1 WHERE 1 IN (SELECT 1, \"abc\")`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Subquery of type IN must have only one output column`),\n\t\t\t},\n\t\t\t// TODO: need to expand struct like buildUnnestView\n\t\t\t// {\n\t\t\t// \tname: \"SubQuery_In_AsStruct\",\n\t\t\t// \tsql:  `SELECT Id FROM Simple WHERE (Id) IN (SELECT AS STRUCT Id From Simple WHERE Id > 100)`,\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{int64(200)},\n\t\t\t// \t\t[]interface{}{int64(300)},\n\t\t\t// \t},\n\t\t\t// },\n\t\t\t{\n\t\t\t\tname: \"SubQuery_EXISTS\",\n\t\t\t\tsql:  `SELECT Id FROM Simple WHERE EXISTS(SELECT 1, \"xx\")`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"SubQuery_EXISTS_NoMatch\",\n\t\t\t\tsql:      `SELECT Id FROM Simple WHERE EXISTS(SELECT * FROM Simple WHERE Id = 1000)`,\n\t\t\t\texpected: nil,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"SubQuery_ColumnAlias\",\n\t\t\t\tsql:   `SELECT Id, foo, bar FROM (SELECT Id, Id AS foo, Id bar FROM Simple)`,\n\t\t\t\tnames: []string{\"Id\", \"foo\", \"bar\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), int64(100), int64(100)},\n\t\t\t\t\t[]interface{}{int64(200), int64(200), int64(200)},\n\t\t\t\t\t[]interface{}{int64(300), int64(300), int64(300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"SubQuery_ColumnAlias2\",\n\t\t\t\tsql:   `SELECT Id +1, foo +1, bar +1 FROM (SELECT Id, Id AS foo, Id bar FROM Simple)`,\n\t\t\t\tnames: []string{\"\", \"\", \"\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(101), int64(101), int64(101)},\n\t\t\t\t\t[]interface{}{int64(201), int64(201), int64(201)},\n\t\t\t\t\t[]interface{}{int64(301), int64(301), int64(301)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"SubQuery_ColumnAlias_AsAlias\",\n\t\t\t\tsql:   `SELECT x AS y FROM (SELECT Id x FROM Simple)`,\n\t\t\t\tnames: []string{\"y\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"SubQuery_TableAlias\",\n\t\t\t\tsql:  `SELECT xx.Id FROM (SELECT Id FROM Simple) AS xx`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t},\n\t\t\t},\n\n\t\t\t{\n\t\t\t\tname: \"Compound_Union_Distinct\",\n\t\t\t\tsql:  `SELECT Id FROM Simple UNION DISTINCT SELECT Id FROM Simple`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_Union_All\",\n\t\t\t\tsql:  `SELECT Id FROM Simple UNION ALL SELECT Id FROM Simple`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_Union_All_SelectLimit\",\n\t\t\t\tsql:  `SELECT Id FROM Simple UNION ALL (SELECT Id FROM Simple LIMIT 1)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_Union_All_UnionLimit\",\n\t\t\t\tsql:  `SELECT Id FROM Simple UNION ALL (SELECT Id FROM Simple) LIMIT 5`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_Union_All_OrderBy\",\n\t\t\t\tsql:  `SELECT Id FROM Simple UNION ALL (SELECT Id FROM Simple) ORDER BY Id`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_Union_All_OrderBy_Limit\",\n\t\t\t\tsql:  `SELECT Id FROM Simple UNION ALL (SELECT Id FROM Simple) ORDER BY Id LIMIT 3`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_Intersect_Distinct\",\n\t\t\t\tsql:  `SELECT Id FROM Simple INTERSECT DISTINCT SELECT Id FROM Simple`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_Intersect_Distinct2\",\n\t\t\t\tsql:  `SELECT Id FROM Simple INTERSECT DISTINCT (SELECT Id FROM Simple WHERE Id IN (100, 300))`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_Intersect_Distinct_Limit\",\n\t\t\t\tsql:  `SELECT Id FROM Simple INTERSECT DISTINCT (SELECT Id FROM Simple) LIMIT 2`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_Intersect_Distinct_OrderBy\",\n\t\t\t\tsql:  `SELECT Id FROM Simple INTERSECT DISTINCT (SELECT Id FROM Simple) ORDER BY Id DESC`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"Compound_Except_Distinct\",\n\t\t\t\tsql:      `SELECT Id FROM Simple EXCEPT DISTINCT SELECT Id FROM Simple`,\n\t\t\t\texpected: nil,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_Except_Distinct2\",\n\t\t\t\tsql:  `SELECT Id FROM Simple EXCEPT DISTINCT (SELECT Id FROM Simple WHERE Id IN (100, 300))`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_Except_Distinct_Limit\",\n\t\t\t\tsql:  `SELECT Id FROM Simple EXCEPT DISTINCT (SELECT Id FROM Simple WHERE Id = 200) LIMIT 1`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_Except_Distinct_OrderBy\",\n\t\t\t\tsql:  `SELECT Id FROM Simple EXCEPT DISTINCT (SELECT Id FROM Simple WHERE Id = 200) ORDER BY Id DESC`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_Complex1\",\n\t\t\t\tsql:  `SELECT Id FROM Simple UNION ALL SELECT Id+2 FROM Simple UNION ALL SELECT Id+1 FROM Simple`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t\t[]interface{}{int64(102)},\n\t\t\t\t\t[]interface{}{int64(202)},\n\t\t\t\t\t[]interface{}{int64(302)},\n\t\t\t\t\t[]interface{}{int64(101)},\n\t\t\t\t\t[]interface{}{int64(201)},\n\t\t\t\t\t[]interface{}{int64(301)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_Complex2\",\n\t\t\t\tsql:  `SELECT Id FROM Simple UNION ALL SELECT Id+1 FROM Simple UNION ALL SELECT Id+2 FROM Simple ORDER BY Id DESC`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(302)},\n\t\t\t\t\t[]interface{}{int64(301)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t\t[]interface{}{int64(202)},\n\t\t\t\t\t[]interface{}{int64(201)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(102)},\n\t\t\t\t\t[]interface{}{int64(101)},\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_Complex3\",\n\t\t\t\tsql:  `(SELECT Id FROM Simple UNION ALL SELECT Id+1 FROM Simple) INTERSECT DISTINCT SELECT Id FROM Simple`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// TODO: INT64 and FLOAT64 are compatible value type\n\t\t\t// {\n\t\t\t// \tname: \"Compound_MergeIntFloat\",\n\t\t\t// \tsql:  `SELECT 1 UNION ALL SELECT 0.1`,\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{float64(1)},\n\t\t\t// \t\t[]interface{}{float64(0.1)},\n\t\t\t// \t},\n\t\t\t// },\n\t\t\t// {\n\t\t\t// \tname: \"Compound_MergeIntFloat2\",\n\t\t\t// \tsql:  `SELECT 1 UNION DISTINCT SELECT 1.0`,\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{float64(1.0)},\n\t\t\t// \t},\n\t\t\t// },\n\t\t\t{\n\t\t\t\tname: \"Compound_ColumnsNum\",\n\t\t\t\tsql:  `SELECT 1 UNION ALL SELECT 1, 2`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Queries in UNION ALL have mismatched column count; query 1 has 1 column, query 2 has 2 columns`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_ColumnsNum2\",\n\t\t\t\tsql:  `SELECT 1, 2 INTERSECT DISTINCT SELECT 1`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Queries in INTERSECT DISTINCT have mismatched column count; query 1 has 2 column, query 2 has 1 columns`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_ColumnsType\",\n\t\t\t\tsql:  `SELECT 1 EXCEPT DISTINCT SELECT \"xx\"`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Column 1 in EXCEPT DISTINCT has incompatible types: INT64, STRING`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Compound_ColumnsType2\",\n\t\t\t\tsql:  `SELECT 1, 0.1 EXCEPT DISTINCT SELECT 1, true`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Column 2 in EXCEPT DISTINCT has incompatible types: FLOAT64, BOOL`),\n\t\t\t},\n\t\t},\n\n\t\t\"BinaryOp\": {\n\t\t\t{\n\t\t\t\tname: \"Equal_IntInt\",\n\t\t\t\tsql:  `SELECT 1 = 1`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{true},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Equal_IntFloat1\",\n\t\t\t\tsql:  `SELECT 1 = 1.0`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{true},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Equal_IntFloat2\",\n\t\t\t\tsql:  `SELECT 1 = 1.000000000000000000000000000000000000001`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{true},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Equal_IntFloat3\",\n\t\t\t\tsql:  `SELECT 1 = 1.1`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{false},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Equal_IntString\",\n\t\t\t\tsql:  `SELECT 1 = \"1\"`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^No matching signature for operator = for argument types: INT64, STRING.`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Equal_IntBool\",\n\t\t\t\tsql:  `SELECT 1 = TRUE`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^No matching signature for operator = for argument types: INT64, BOOL.`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Equal_FloatString\",\n\t\t\t\tsql:  `SELECT 1.0 = \"1.0\"`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^No matching signature for operator = for argument types: FLOAT64, STRING.`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Equal_StringFloat\",\n\t\t\t\tsql:  `SELECT \"1.0\" = 1.0`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^No matching signature for operator = for argument types: STRING, FLOAT64.`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Equal_StringFloat\",\n\t\t\t\tsql:  `SELECT \"1.0\" = 1.0`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^No matching signature for operator = for argument types: STRING, FLOAT64.`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Equal_StringBool\",\n\t\t\t\tsql:  `SELECT \"TRUE\" = TRUE`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^No matching signature for operator = for argument types: STRING, BOOL.`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Equal_StringBytes\",\n\t\t\t\tsql:  `SELECT \"xxx\" = B'xxx'`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^No matching signature for operator = for argument types: STRING, BYTES.`),\n\t\t\t},\n\n\t\t\t// {\n\t\t\t// \tname: \"Less_StructStruct_Coerce\",\n\t\t\t// \tsql:  `SELECT (100) < (101)`,\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{false},\n\t\t\t// \t},\n\t\t\t// },\n\t\t\t{\n\t\t\t\tname: \"Less_StructStruct\",\n\t\t\t\tsql:  `SELECT (100, 100) < (100, 100)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Less than is not defined for arguments of type STRUCT<INT64, INT64>`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"LessEqual_StructStruct\",\n\t\t\t\tsql:  `SELECT (100, 100) <= (100, 100)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Less than is not defined for arguments of type STRUCT<INT64, INT64>`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Greater_StructStruct\",\n\t\t\t\tsql:  `SELECT (100, 100) > (100, 100)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Greater than is not defined for arguments of type STRUCT<INT64, INT64>`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"GreaterEqual_StructStruct\",\n\t\t\t\tsql:  `SELECT (100, 100) >= (100, 100)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Greater than is not defined for arguments of type STRUCT<INT64, INT64>`),\n\t\t\t},\n\t\t},\n\n\t\t\"Arithmetic\": {\n\t\t\t{\n\t\t\t\tname: \"Arithmetic_Add\",\n\t\t\t\tsql:  `SELECT 1 + 2`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(3)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Arithmetic_Add_Float\",\n\t\t\t\tsql:  `SELECT 1.5 + 2.5`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{float64(4)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Arithmetic_Sub\",\n\t\t\t\tsql:  `SELECT 1 - 2`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(-1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Arithmetic_Sub_Float\",\n\t\t\t\tsql:  `SELECT 1.5 - 2`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{float64(-0.5)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Arithmetic_Mult\",\n\t\t\t\tsql:  `SELECT 2 * 2`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(4)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Arithmetic_Mult_Float\",\n\t\t\t\tsql:  `SELECT 2.5 * 2`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{float64(5)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Arithmetic_Div\",\n\t\t\t\tsql:  `SELECT 3 / 2`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{float64(1.5)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Arithmetic_BitOr\",\n\t\t\t\tsql:  `SELECT 0x11 | 0x04`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(0x15)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Arithmetic_BitXor\",\n\t\t\t\tsql:  `SELECT 0x11 ^ 0x01`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(0x10)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Arithmetic_BitAnd\",\n\t\t\t\tsql:  `SELECT 0x13 & 0x01`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(0x01)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Arithmetic_BitLeftShift\",\n\t\t\t\tsql:  `SELECT 0x3 << 3`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(24)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Arithmetic_BitRightShift\",\n\t\t\t\tsql:  `SELECT 0xf0 >> 2`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(60)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Unary_Int\",\n\t\t\t\tsql:  `SELECT - -1`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Unary_Float\",\n\t\t\t\tsql:  `SELECT - -0.1`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{float64(0.1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Unary_BitNot\",\n\t\t\t\tsql:  `SELECT ~ -3`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(2)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Unary_Not\",\n\t\t\t\tsql:  `SELECT NOT true`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{false},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t\"Cast\": {\n\t\t\t{\n\t\t\t\tname: \"Cast_Int64_String\",\n\t\t\t\tsql:  `SELECT CAST(100 AS STRING)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"100\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Int64_String_Neg\",\n\t\t\t\tsql:  `SELECT CAST(-100 AS STRING)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"-100\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Int64_Bool_True\",\n\t\t\t\tsql:  `SELECT CAST(100 AS BOOL)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{true},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Int64_Bool_True2\",\n\t\t\t\tsql:  `SELECT CAST(-100 AS BOOL)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{true},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Int64_Bool_False\",\n\t\t\t\tsql:  `SELECT CAST(0 AS BOOL)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{false},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Int64_Float64_1\",\n\t\t\t\tsql:  `SELECT CAST(0 AS FLOAT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{float64(0)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Int64_Float64_2\",\n\t\t\t\tsql:  `SELECT CAST(-1 AS FLOAT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{float64(-1.0)},\n\t\t\t\t},\n\t\t\t},\n\n\t\t\t{\n\t\t\t\tname: \"Cast_Float64_String_Big_Small\",\n\t\t\t\tsql:  `SELECT CAST(@foo AS STRING)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(2.3),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"2.3\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Float64_String_Big_Small_neg\",\n\t\t\t\tsql:  `SELECT CAST(@foo AS STRING)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(-2.3),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"-2.3\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Float64_String_Big\",\n\t\t\t\tsql:  `SELECT CAST(@foo AS STRING)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(383260575764816448.0),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"3.8326057576481645e+17\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Float64_String_Inf\",\n\t\t\t\tsql:  `SELECT CAST(@foo AS STRING)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(math.Inf(0)),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"inf\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Float64_String_Inf_Neg\",\n\t\t\t\tsql:  `SELECT CAST(@foo AS STRING)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(math.Inf(-1)),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"-inf\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// TODO\n\t\t\t// {\n\t\t\t// \tname: \"Cast_Float64_String_NaN\",\n\t\t\t// \tsql: `SELECT CAST(@foo AS STRING)`,\n\t\t\t// \tparams: map[string]Value{\n\t\t\t// \t\t\"foo\": makeTestValue(math.NaN()),\n\t\t\t// \t},\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{\"nan\"},\n\t\t\t// \t},\n\t\t\t// },\n\t\t\t{\n\t\t\t\tname: \"Cast_Float64_Int64_Big_Small\",\n\t\t\t\tsql:  `SELECT CAST(@foo AS INT64)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(2.3),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(2)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Float64_Int64_Big_Small_neg\",\n\t\t\t\tsql:  `SELECT CAST(@foo AS INT64)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(-2.3),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(-2)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Float64_Int64_Big\",\n\t\t\t\tsql:  `SELECT CAST(@foo AS INT64)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(383260575764816448.0),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(383260575764816448)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Float64_Int64_Round_Pos\",\n\t\t\t\tsql:  `SELECT CAST(@foo AS INT64)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(1.5),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(2)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Float64_Int64_Round_Neg\",\n\t\t\t\tsql:  `SELECT CAST(@foo AS INT64)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(-0.5),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(-1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Float64_Int64_Inf\",\n\t\t\t\tsql:  `SELECT CAST(@foo AS INT64)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(math.Inf(0)),\n\t\t\t\t},\n\t\t\t\tcode: codes.OutOfRange,\n\t\t\t\tmsg:  regexp.MustCompile(`^Illegal conversion of non-finite floating point number to an integer: inf`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Float64_Int64_Inf_Neg\",\n\t\t\t\tsql:  `SELECT CAST(@foo AS INT64)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(math.Inf(-1)),\n\t\t\t\t},\n\t\t\t\tcode: codes.OutOfRange,\n\t\t\t\tmsg:  regexp.MustCompile(`^Illegal conversion of non-finite floating point number to an integer: -inf`),\n\t\t\t},\n\t\t\t// TODO\n\t\t\t// {\n\t\t\t// \tname: \"Cast_Float64_Int64_NaN\",\n\t\t\t// \tsql:  `SELECT CAST(@foo AS INT64)`,\n\t\t\t// \tparams: map[string]Value{\n\t\t\t// \t\t\"foo\": makeTestValue(math.NaN()),\n\t\t\t// \t},\n\t\t\t// \tcode: codes.OutOfRange,\n\t\t\t// \tmsg:  regexp.MustCompile(`^Illegal conversion of non-finite floating point number to an integer: nan`),\n\t\t\t// },\n\n\t\t\t{\n\t\t\t\tname: \"Cast_Bool_String_True\",\n\t\t\t\tsql:  `SELECT CAST(true AS STRING)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"TRUE\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Bool_String_False\",\n\t\t\t\tsql:  `SELECT CAST(false AS STRING)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"FALSE\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Bool_Int64_True\",\n\t\t\t\tsql:  `SELECT CAST(true AS INT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Bool_Int64_False\",\n\t\t\t\tsql:  `SELECT CAST(false AS INT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(0)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Bool_True\",\n\t\t\t\tsql:  `SELECT CAST(\"TRUE\" AS BOOL)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{true},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Bool_True2\",\n\t\t\t\tsql:  `SELECT CAST(\"TrUe\" AS BOOL)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{true},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Bool_False\",\n\t\t\t\tsql:  `SELECT CAST(\"FALSE\" AS BOOL)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{false},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Bool_False2\",\n\t\t\t\tsql:  `SELECT CAST(\"faLsE\" AS BOOL)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{false},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Bool_Invalid\",\n\t\t\t\tsql:  `SELECT CAST(\"xx\" AS BOOL)`,\n\t\t\t\tcode: codes.OutOfRange,\n\t\t\t\tmsg:  regexp.MustCompile(`^Bad bool value: xx`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Int64_Base10_1\",\n\t\t\t\tsql:  `SELECT CAST(\"100\" AS INT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Int64_Base10_2\",\n\t\t\t\tsql:  `SELECT CAST(\"-100\" AS INT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(-100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Int64_Base10_3\",\n\t\t\t\tsql:  `SELECT CAST(\"+100\" AS INT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Int64_Base10_4\",\n\t\t\t\tsql:  `SELECT CAST(\"0100\" AS INT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Int64_Base10_5\",\n\t\t\t\tsql:  `SELECT CAST(\"00100\" AS INT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Int64_Base16_1\",\n\t\t\t\tsql:  `SELECT CAST(\"0x100\" AS INT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(256)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Int64_Base16_2\",\n\t\t\t\tsql:  `SELECT CAST(\"-0x100\" AS INT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(-256)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Int64_Base16_3\",\n\t\t\t\tsql:  `SELECT CAST(\"0xABC\" AS INT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(2748)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Int64_Invalid1\",\n\t\t\t\tsql:  `SELECT CAST(\"- 100\" AS INT64)`,\n\t\t\t\tcode: codes.OutOfRange,\n\t\t\t\tmsg:  regexp.MustCompile(`^Bad int64 value: - 100`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Int64_Invalid2\",\n\t\t\t\tsql:  `SELECT CAST(\"00x100\" AS INT64)`,\n\t\t\t\tcode: codes.OutOfRange,\n\t\t\t\tmsg:  regexp.MustCompile(`^Bad int64 value: 00x100`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Float64_1\",\n\t\t\t\tsql:  `SELECT CAST(\"123.456e-67\" AS FLOAT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{float64(1.23456e-65)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Float64_2\",\n\t\t\t\tsql:  `SELECT CAST(\".1E4\" AS FLOAT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{float64(1000)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Float64_3\",\n\t\t\t\tsql:  `SELECT CAST(\"58.\" AS FLOAT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{float64(58)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Float64_4\",\n\t\t\t\tsql:  `SELECT CAST(\"4e2\" AS FLOAT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{float64(400)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// {\n\t\t\t// \tname: \"Cast_String_Float64_Nan1\",\n\t\t\t// \tsql: `SELECT CAST(\"NaN\" AS FLOAT64)`,\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{math.NaN()},\n\t\t\t// \t},\n\t\t\t// },\n\t\t\t// {\n\t\t\t// \tname: \"Cast_String_Float64_Nan2\",\n\t\t\t// \tsql: `SELECT CAST(\"nan\" AS FLOAT64)`,\n\t\t\t// \texpected: [][]interface{}{\n\t\t\t// \t\t[]interface{}{math.NaN()},\n\t\t\t// \t},\n\t\t\t// },\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Float64_Inf1\",\n\t\t\t\tsql:  `SELECT CAST(\"inf\" AS FLOAT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{math.Inf(0)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Float64_Inf2\",\n\t\t\t\tsql:  `SELECT CAST(\"+inf\" AS FLOAT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{math.Inf(0)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Float64_Inf3\",\n\t\t\t\tsql:  `SELECT CAST(\"-inf\" AS FLOAT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{math.Inf(-1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Float64_Inf4\",\n\t\t\t\tsql:  `SELECT CAST(\"-Inf\" AS FLOAT64)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{math.Inf(-1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Float64_Invalid\",\n\t\t\t\tsql:  `SELECT CAST(\"xx\" AS FLOAT64)`,\n\t\t\t\tcode: codes.OutOfRange,\n\t\t\t\tmsg:  regexp.MustCompile(`^Bad double value: xx`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Date_Valid\",\n\t\t\t\tsql:  `SELECT CAST(\"1999-01-01\" AS DATE)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-01\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Date_Valid2\",\n\t\t\t\tsql:  `SELECT CAST(\"1999-4-5\" AS DATE)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-04-05\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_DATE_Invalid\",\n\t\t\t\tsql:  `SELECT CAST(\"x\" AS DATE)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Could not cast literal \"x\" to type DATE`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_DATE_Invalid2\",\n\t\t\t\tsql:  `SELECT CAST(\"1999\" AS DATE)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Could not cast literal \"1999\" to type DATE`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_DATE_Invalid3\",\n\t\t\t\tsql:  `SELECT CAST(\"1999-01-50\" AS DATE)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Could not cast literal \"1999-01-50\" to type DATE`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Timestamp_Valid\",\n\t\t\t\tsql:  `SELECT CAST(\"1999-01-02T20:34:56Z\" AS TIMESTAMP)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T20:34:56Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_Timestamp_Valid2\",\n\t\t\t\tsql:  `SELECT CAST(\"1999-01-02 01:02:03.123456\" AS TIMESTAMP)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02T09:02:03.123456Z\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_TIMESTAMP_Invalid\",\n\t\t\t\tsql:  `SELECT CAST(\"x\" AS TIMESTAMP)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Could not cast literal \"x\" to type TIMESTAMP`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_TIMESTAMP_Invalid2\",\n\t\t\t\tsql:  `SELECT CAST(\"1999\" AS TIMESTAMP)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Could not cast literal \"1999\" to type TIMESTAMP`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_String_TIMESTAMP_Invalid3\",\n\t\t\t\tsql:  `SELECT CAST(\"1999-01-50\" AS TIMESTAMP)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^Could not cast literal \"1999-01-50\" to type TIMESTAMP`),\n\t\t\t},\n\n\t\t\t{\n\t\t\t\tname: \"Cast_Date_String_Valid\",\n\t\t\t\tsql:  `SELECT CAST(DATE \"1999-1-1\" AS STRING)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-01\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Date_Timestamp_Valid\",\n\t\t\t\tsql:  `SELECT CAST(DATE \"1999-01-01\" AS TIMESTAMP)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-01T08:00:00Z\"},\n\t\t\t\t},\n\t\t\t},\n\n\t\t\t{\n\t\t\t\tname: \"Cast_Timestamp_String_Valid\",\n\t\t\t\tsql:  `SELECT CAST(TIMESTAMP \"1999-01-02 00:00:00\" AS STRING)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02 00:00:00-08\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Timestamp_String_Valid2\",\n\t\t\t\tsql:  `SELECT CAST(TIMESTAMP \"1999-01-02 00:00:00Z\" AS STRING)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-01 16:00:00-08\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Timestamp_String_Valid3\",\n\t\t\t\tsql:  `SELECT CAST(TIMESTAMP \"1999-01-02 00:00:00.123456789Z\" AS STRING)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-01 16:00:00.123456789-08\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Timestamp_Date_Valid\",\n\t\t\t\tsql:  `SELECT CAST(TIMESTAMP \"1999-01-02 00:00:00\" AS DATE)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-02\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Cast_Timestamp_Date_Valid2\",\n\t\t\t\tsql:  `SELECT CAST(TIMESTAMP \"1999-01-02 00:00:00Z\" AS DATE)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"1999-01-01\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t\"Function\": {\n\t\t\t{\n\t\t\t\tname: \"Function_Count\",\n\t\t\t\tsql:  `SELECT COUNT(1) FROM Simple`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(3)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Count2\",\n\t\t\t\tsql:  `SELECT COUNT(Id) FROM Simple`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(3)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Count3\",\n\t\t\t\tsql:  `SELECT COUNT(Id) AS count FROM Simple`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(3)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Count4\",\n\t\t\t\tsql:  `SELECT COUNT(\"x\") FROM Simple`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(3)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Count5\",\n\t\t\t\tsql:  `SELECT COUNT(NULL) FROM Simple`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(0)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Count_Param\",\n\t\t\t\tsql:  `SELECT COUNT(@foo) FROM Simple`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(200),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(3)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Sign\",\n\t\t\t\tsql:  `SELECT SIGN(1)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Sign2\",\n\t\t\t\tsql:  `SELECT SIGN(-1)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(-1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Sign3\",\n\t\t\t\tsql:  `SELECT SIGN(0)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(0)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_StartsWith\",\n\t\t\t\tsql:  `SELECT STARTS_WITH(\"abc\", \"ab\")`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{true},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_StartsWith2\",\n\t\t\t\tsql:  `SELECT STARTS_WITH(\"abc\", \"xx\")`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{false},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_StartsWith_Param\",\n\t\t\t\tsql:  `SELECT STARTS_WITH(@a, @b)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"a\": makeTestValue(\"xyz\"),\n\t\t\t\t\t\"b\": makeTestValue(\"xy\"),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{true},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Max_Int\",\n\t\t\t\tsql:  `SELECT MAX(x) FROM UNNEST([100, 200, 300]) AS x`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(300)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Max_String\",\n\t\t\t\tsql:  `SELECT MAX(x) FROM UNNEST([\"xxx\", \"zz\", \"yy\"]) AS x`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"zz\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Min_Int\",\n\t\t\t\tsql:  `SELECT MIN(x) FROM UNNEST([100, 200, 300]) AS x`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Min_String\",\n\t\t\t\tsql:  `SELECT MIN(x) FROM UNNEST([\"xxx\", \"zz\", \"yy\"]) AS x`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Avg\",\n\t\t\t\tsql:  `SELECT AVG(x) as avg FROM UNNEST([100, 200, NULL, 300, 100]) AS x`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{float64(175)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Avg_Distinct\",\n\t\t\t\tsql:  `SELECT AVG(DISTINCT x) as avg FROM UNNEST([100, 200, NULL, 300, 100]) AS x`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{float64(200)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Sum\",\n\t\t\t\tsql:  `SELECT SUM(x) as avg FROM UNNEST([100, 200, 300]) AS x`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(600)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// TODO: SUM with float\n\n\t\t\t{\n\t\t\t\tname: \"Function_Concat\",\n\t\t\t\tsql:  `SELECT CONCAT(\"xx\", \"yy\")`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\"xxyy\"},\n\t\t\t\t},\n\t\t\t},\n\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_Timestamp\",\n\t\t\t\tsql: `SELECT` +\n\t\t\t\t\t` EXTRACT(NANOSECOND  FROM t AT TIME ZONE \"UTC\"),` +\n\t\t\t\t\t` EXTRACT(MICROSECOND FROM t AT TIME ZONE \"UTC\"),` +\n\t\t\t\t\t` EXTRACT(MILLISECOND FROM t AT TIME ZONE \"UTC\"),` +\n\t\t\t\t\t` EXTRACT(SECOND      FROM t AT TIME ZONE \"UTC\"),` +\n\t\t\t\t\t` EXTRACT(MINUTE      FROM t AT TIME ZONE \"UTC\"),` +\n\t\t\t\t\t` EXTRACT(HOUR        FROM t AT TIME ZONE \"UTC\"),` +\n\t\t\t\t\t` EXTRACT(DAYOFWEEK   FROM t AT TIME ZONE \"UTC\"),` +\n\t\t\t\t\t` EXTRACT(DAY         FROM t AT TIME ZONE \"UTC\"),` +\n\t\t\t\t\t` EXTRACT(DAYOFYEAR   FROM t AT TIME ZONE \"UTC\"),` +\n\t\t\t\t\t` EXTRACT(ISOWEEK     FROM t AT TIME ZONE \"UTC\"),` +\n\t\t\t\t\t` EXTRACT(QUARTER     FROM t AT TIME ZONE \"UTC\"),` +\n\t\t\t\t\t` EXTRACT(YEAR        FROM t AT TIME ZONE \"UTC\"),` +\n\t\t\t\t\t` EXTRACT(ISOYEAR     FROM t AT TIME ZONE \"UTC\"),` +\n\t\t\t\t\t` EXTRACT(DATE        FROM t AT TIME ZONE \"UTC\")` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-02 12:34:56.987654321Z' t)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\tint64(987654321), int64(987654), int64(987),\n\t\t\t\t\t\tint64(56), int64(34), int64(12), // sec,min,hour\n\t\t\t\t\t\tint64(1), int64(2), int64(2), // dayofweek, day, dayofyear\n\t\t\t\t\t\tint64(1),                           // isoweek\n\t\t\t\t\t\tint64(1), int64(2012), int64(2012), // quarter, year, isoyear\n\t\t\t\t\t\t\"2012-01-02\", // date\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_TimeZone\",\n\t\t\t\tsql: `SELECT` +\n\t\t\t\t\t` EXTRACT(DAY  FROM t AT TIME ZONE \"UTC\"),` +\n\t\t\t\t\t` EXTRACT(HOUR FROM t AT TIME ZONE \"UTC\"),` +\n\t\t\t\t\t` EXTRACT(DAY  FROM t AT TIME ZONE \"Asia/Tokyo\"),` +\n\t\t\t\t\t` EXTRACT(HOUR FROM t AT TIME ZONE \"Asia/Tokyo\"),` +\n\t\t\t\t\t` EXTRACT(DAY  FROM t AT TIME ZONE \"America/Los_Angeles\"),` +\n\t\t\t\t\t` EXTRACT(HOUR FROM t AT TIME ZONE \"America/Los_Angeles\")` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-01 00:00:00.999999999Z' t)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1), int64(0), int64(1), int64(9), int64(31), int64(16)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_TimeZone_Invalid\",\n\t\t\t\tsql: `SELECT EXTRACT(HOUR FROM t AT TIME ZONE \"xxx\")` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-01 00:00:00.999999999Z' t)`,\n\t\t\t\tcode: codes.OutOfRange,\n\t\t\t\tmsg:  regexp.MustCompile(`^Invalid time zone: xxx`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_FixedTimeZone\",\n\t\t\t\tsql: `SELECT` +\n\t\t\t\t\t` EXTRACT(HOUR   FROM t AT TIME ZONE \"+01:20\"),` +\n\t\t\t\t\t` EXTRACT(MINUTE FROM t AT TIME ZONE \"+01:20\"),` +\n\t\t\t\t\t` EXTRACT(HOUR   FROM t AT TIME ZONE \"-01:20\"),` +\n\t\t\t\t\t` EXTRACT(MINUTE FROM t AT TIME ZONE \"-01:20\")` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-01 12:00:00.999999999Z' t)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(13), int64(20), int64(10), int64(40)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_FixedTimeZone2\",\n\t\t\t\tsql: `SELECT` +\n\t\t\t\t\t` EXTRACT(HOUR   FROM t AT TIME ZONE \"+1\"),` +\n\t\t\t\t\t` EXTRACT(MINUTE FROM t AT TIME ZONE \"+1\"),` +\n\t\t\t\t\t` EXTRACT(HOUR   FROM t AT TIME ZONE \"-1\"),` +\n\t\t\t\t\t` EXTRACT(MINUTE FROM t AT TIME ZONE \"-1\")` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-01 12:00:00.999999999Z' t)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(13), int64(0), int64(11), int64(0)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_FixedTimeZone3\",\n\t\t\t\tsql: `SELECT` +\n\t\t\t\t\t` EXTRACT(HOUR   FROM t AT TIME ZONE \"+11\"),` +\n\t\t\t\t\t` EXTRACT(MINUTE FROM t AT TIME ZONE \"+11\"),` +\n\t\t\t\t\t` EXTRACT(HOUR   FROM t AT TIME ZONE \"-11\"),` +\n\t\t\t\t\t` EXTRACT(MINUTE FROM t AT TIME ZONE \"-11\")` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-01 12:00:00.999999999Z' t)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(23), int64(0), int64(1), int64(0)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_FixedTimeZone4\",\n\t\t\t\tsql: `SELECT` +\n\t\t\t\t\t` EXTRACT(HOUR   FROM t AT TIME ZONE \"+11:3\"),` +\n\t\t\t\t\t` EXTRACT(MINUTE FROM t AT TIME ZONE \"+11:3\"),` +\n\t\t\t\t\t` EXTRACT(HOUR   FROM t AT TIME ZONE \"-11:3\"),` +\n\t\t\t\t\t` EXTRACT(MINUTE FROM t AT TIME ZONE \"-11:3\")` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-01 12:00:00.999999999Z' t)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(23), int64(3), int64(0), int64(57)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_FixedTimeZone5\",\n\t\t\t\tsql: `SELECT` +\n\t\t\t\t\t` EXTRACT(HOUR   FROM t AT TIME ZONE \"+1:35\"),` +\n\t\t\t\t\t` EXTRACT(MINUTE FROM t AT TIME ZONE \"+1:35\"),` +\n\t\t\t\t\t` EXTRACT(HOUR   FROM t AT TIME ZONE \"-1:35\"),` +\n\t\t\t\t\t` EXTRACT(MINUTE FROM t AT TIME ZONE \"-1:35\")` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-01 12:00:00.999999999Z' t)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(13), int64(35), int64(10), int64(25)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_FixedTimeZone_Invalid\",\n\t\t\t\tsql: `SELECT EXTRACT(HOUR FROM t AT TIME ZONE \"11:11\")` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-01 12:00:00.999999999Z' t)`,\n\t\t\t\tcode: codes.OutOfRange,\n\t\t\t\tmsg:  regexp.MustCompile(`^Invalid time zone: 11:11`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_FixedTimeZone_Invalid2\",\n\t\t\t\tsql: `SELECT EXTRACT(HOUR FROM t AT TIME ZONE \"+:11\")` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-01 12:00:00.999999999Z' t)`,\n\t\t\t\tcode: codes.OutOfRange,\n\t\t\t\tmsg:  regexp.MustCompile(`^Invalid time zone: \\+:11`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_FixedTimeZone_Invalid3\",\n\t\t\t\tsql: `SELECT EXTRACT(HOUR FROM t AT TIME ZONE \"+11:\")` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-01 12:00:00.999999999Z' t)`,\n\t\t\t\tcode: codes.OutOfRange,\n\t\t\t\tmsg:  regexp.MustCompile(`^Invalid time zone: \\+11:`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_FixedTimeZone_Invalid4\",\n\t\t\t\tsql: `SELECT EXTRACT(HOUR FROM t AT TIME ZONE \"+\")` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-01 12:00:00.999999999Z' t)`,\n\t\t\t\tcode: codes.OutOfRange,\n\t\t\t\tmsg:  regexp.MustCompile(`^Invalid time zone: \\+`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_FixedTimeZone_Invalid5\",\n\t\t\t\tsql: `SELECT EXTRACT(HOUR FROM t AT TIME ZONE \"+25:00\")` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-01 12:00:00.999999999Z' t)`,\n\t\t\t\tcode: codes.OutOfRange,\n\t\t\t\tmsg:  regexp.MustCompile(`^Invalid time zone: \\+25:00`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_FixedTimeZone_Invalid6\",\n\t\t\t\tsql: `SELECT EXTRACT(HOUR FROM t AT TIME ZONE \"+10:61\")` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-01 12:00:00.999999999Z' t)`,\n\t\t\t\tcode: codes.OutOfRange,\n\t\t\t\tmsg:  regexp.MustCompile(`^Invalid time zone: \\+10:61`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_Timestamp_WithoutTimezone\",\n\t\t\t\tsql: `SELECT EXTRACT(HOUR FROM t)` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-02 12:34:56.987654321Z' t)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^handy-spanner: please specify timezone explicitly.`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_Timestamp_TimeZone_ByParam\",\n\t\t\t\tsql: `SELECT EXTRACT(HOUR FROM t AT TIME ZONE @foo)` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-02 12:34:56.987654321Z' t)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(\"UTC\"),\n\t\t\t\t},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(12)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_Timestamp_TimeZone_InvalidType\",\n\t\t\t\tsql: `SELECT EXTRACT(HOUR FROM t AT TIME ZONE @foo)` +\n\t\t\t\t\t` FROM (SELECT TIMESTAMP '2012-01-02 12:34:56.987654321Z' t)`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(int64(100)),\n\t\t\t\t},\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^No matching signature for function EXTRACT for argument types: DATE_TIME_PART FROM TIMESTAMP AT TIME ZONE INT64. Supported signatures`),\n\t\t\t},\n\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_Date\",\n\t\t\t\tsql: `SELECT` +\n\t\t\t\t\t` EXTRACT(DAYOFWEEK FROM t),` +\n\t\t\t\t\t` EXTRACT(DAY       FROM t),` +\n\t\t\t\t\t` EXTRACT(DAYOFYEAR FROM t),` +\n\t\t\t\t\t` EXTRACT(ISOWEEK   FROM t),` +\n\t\t\t\t\t` EXTRACT(QUARTER   FROM t),` +\n\t\t\t\t\t` EXTRACT(YEAR      FROM t),` +\n\t\t\t\t\t` EXTRACT(ISOYEAR   FROM t)` +\n\t\t\t\t\t` FROM (SELECT DATE '2012-01-02' t)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\tint64(1), int64(2), int64(2), // dayofweek, day, dayofyear\n\t\t\t\t\t\tint64(1),                           // isoweek\n\t\t\t\t\t\tint64(1), int64(2012), int64(2012), // quarter, year, isoyear\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_Date_InvalidPart_Nano\",\n\t\t\t\tsql: `SELECT EXTRACT(NANOSECOND FROM t)` +\n\t\t\t\t\t` FROM (SELECT DATE '2012-01-02' t)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^EXTRACT from DATE does not support the NANOSECOND date part`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_Date_InvalidPart_Date\",\n\t\t\t\tsql: `SELECT EXTRACT(DATE FROM t)` +\n\t\t\t\t\t` FROM (SELECT DATE '2012-01-02' t)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^EXTRACT from DATE does not support the DATE date part`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_Extract_Date_WithTimeZone\",\n\t\t\t\tsql: `SELECT EXTRACT(DAY FROM t AT TIME ZONE \"UTC\")` +\n\t\t\t\t\t` FROM (SELECT DATE '2012-01-02' t)`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^EXTRACT from DATE does not support AT TIME ZONE`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"IFNULL_Null_IntLiteral\",\n\t\t\t\tsql:   `SELECT IFNULL(NULL, 1) as result`,\n\t\t\t\tnames: []string{\"result\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"IFNULL_IntLiteral_Null\",\n\t\t\t\tsql:   `SELECT IFNULL(2, 0) as result`,\n\t\t\t\tnames: []string{\"result\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(2)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"IFNULL_IntLiteral_IntLiteral\",\n\t\t\t\tsql:   `SELECT IFNULL(1, 2) as result`,\n\t\t\t\tnames: []string{\"result\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"IFNULL_Null_Null\",\n\t\t\t\tsql:   `SELECT IFNULL(NULL, NULL) as result`,\n\t\t\t\tnames: []string{\"result\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{nil},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"IFNULL_Param_Null\",\n\t\t\t\tsql:  `SELECT IFNULL(@foo, NULL) as result`,\n\t\t\t\tparams: map[string]Value{\n\t\t\t\t\t\"foo\": makeTestValue(int64(100)),\n\t\t\t\t},\n\t\t\t\tnames: []string{\"result\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"IFNULL_SubQuery_IntLiteral\",\n\t\t\t\tsql:   `SELECT IFNULL((SELECT NULL), 3) as result`,\n\t\t\t\tnames: []string{\"result\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(3)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"IFNULL_NotMatchSignature_Int_String\",\n\t\t\t\tsql:  `SELECT IFNULL(1, \"a\") as result`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^arguments does not match for IFNULL`), // TODO\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"NULLIF_Equal\",\n\t\t\t\tsql:   `SELECT NULLIF(0, 0) as result`,\n\t\t\t\tnames: []string{\"result\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{nil},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"NULLIF_NotEqual\",\n\t\t\t\tsql:   `SELECT NULLIF(1, 0) as result`,\n\t\t\t\tnames: []string{\"result\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"NULLIF_NotEqual2\",\n\t\t\t\tsql:   `SELECT NULLIF(0, 1) as result`,\n\t\t\t\tnames: []string{\"result\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(0)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"NULLIF_NotMatchSignature_Int_String\",\n\t\t\t\tsql:  `SELECT NULLIF(1, \"a\") as result`,\n\t\t\t\tcode: codes.InvalidArgument,\n\t\t\t\tmsg:  regexp.MustCompile(`^arguments does not match for NULLIF`), // TODO\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:  \"Function_Mod\",\n\t\t\t\tsql:   `SELECT MOD(21, 2) as result`,\n\t\t\t\tnames: []string{\"result\"},\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_GENERATE_ARRAY\",\n\t\t\t\tsql:  `SELECT GENERATE_ARRAY(1, 3)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t{[]int64{1, 2, 3}},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Function_GENERATE_ARRAY2\",\n\t\t\t\tsql:  `SELECT GENERATE_ARRAY(1, 10, 2)`,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t{[]int64{1, 3, 5, 7, 9}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tcs := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\ttcs := tcs\n\t\t\tt.Parallel()\n\t\t\tfor _, tc := range tcs {\n\t\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\t\ttc := tc\n\t\t\t\t\tses := newSession(db, \"foo\")\n\t\t\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\t\t\tstmt, err := (&memefish.Parser{\n\t\t\t\t\t\t\tLexer: &memefish.Lexer{\n\t\t\t\t\t\t\t\tFile: &token.File{FilePath: \"\", Buffer: tc.sql},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}).ParseQuery()\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Fatalf(\"failed to parse sql: %q %v\", tc.sql, err)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// The test case expects OK, it checks respons values.\n\t\t\t\t\t\t// otherwise it checks the error code and the error message.\n\t\t\t\t\t\tif tc.code == codes.OK {\n\t\t\t\t\t\t\tit, err := db.Query(ctx, tx, stmt, tc.params)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tt.Fatalf(\"Query failed: %v\", err)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tvar rows [][]interface{}\n\t\t\t\t\t\t\terr = it.Do(func(row []interface{}) error {\n\t\t\t\t\t\t\t\trows = append(rows, row)\n\t\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif diff := cmp.Diff(tc.expected, rows); diff != \"\" {\n\t\t\t\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// TODO: add names to all test cases. now this is optional check\n\t\t\t\t\t\t\tif tc.names != nil {\n\t\t\t\t\t\t\t\tvar gotnames []string\n\t\t\t\t\t\t\t\tfor _, item := range it.ResultSet() {\n\t\t\t\t\t\t\t\t\tgotnames = append(gotnames, item.Name)\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif diff := cmp.Diff(tc.names, gotnames); diff != \"\" {\n\t\t\t\t\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tit, err := db.Query(ctx, tx, stmt, tc.params)\n\t\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\t\terr = it.Do(func([]interface{}) error {\n\t\t\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tst := status.Convert(err)\n\t\t\t\t\t\t\tif st.Code() != tc.code {\n\t\t\t\t\t\t\t\tt.Errorf(\"expect code to be %v but got %v\", tc.code, st.Code())\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif !tc.msg.MatchString(st.Message()) {\n\t\t\t\t\t\t\t\tt.Errorf(\"unexpected error message: \\n %q\\n expected:\\n %q\", st.Message(), tc.msg)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestQuery_ArbitraryAssertion(t *testing.T) {\n\tctx := context.Background()\n\tdb := newDatabase()\n\tfor _, s := range allSchema {\n\t\tddls := parseDDL(t, s)\n\t\tfor _, ddl := range ddls {\n\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t}\n\t}\n\n\ttable := []struct {\n\t\tname   string\n\t\tsql    string\n\t\tparams map[string]Value\n\t\tassert func(t *testing.T, rows [][]interface{})\n\t}{\n\t\t{\n\t\t\tname: \"Sample\",\n\t\t\tsql:  `SELECT 1`,\n\t\t\tassert: func(t *testing.T, rows [][]interface{}) {\n\t\t\t\texpected := [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(1)},\n\t\t\t\t}\n\n\t\t\t\tif diff := cmp.Diff(expected, rows); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"CURRENT_TIMESTAMP\",\n\t\t\tsql:  `SELECT CURRENT_TIMESTAMP()`,\n\t\t\tassert: func(t *testing.T, rows [][]interface{}) {\n\t\t\t\tif len(rows) != 1 {\n\t\t\t\t\tt.Fatalf(\"the number of rows should be 1 but got %v\", len(rows))\n\t\t\t\t}\n\t\t\t\tif len(rows[0]) != 1 {\n\t\t\t\t\tt.Fatalf(\"the number of columns should be 1 but got %v\", len(rows[0]))\n\t\t\t\t}\n\n\t\t\t\tts, ok := rows[0][0].(string)\n\t\t\t\tif !ok {\n\t\t\t\t\tt.Fatalf(\"the column should be string but %T\", rows[0][0])\n\t\t\t\t}\n\n\t\t\t\tts2, ok := parseTimestampLiteral(ts)\n\t\t\t\tif !ok {\n\t\t\t\t\tt.Fatalf(\"failed to parse timestamp: %v\", ts)\n\t\t\t\t}\n\n\t\t\t\tif time.Since(ts2) > 3*time.Second {\n\t\t\t\t\tt.Fatalf(\"unexpected time: %v, %v\", ts2, time.Now())\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range table {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttc := tc\n\t\t\tses := newSession(db, \"foo\")\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tstmt, err := (&memefish.Parser{\n\t\t\t\t\tLexer: &memefish.Lexer{\n\t\t\t\t\t\tFile: &token.File{FilePath: \"\", Buffer: tc.sql},\n\t\t\t\t\t},\n\t\t\t\t}).ParseQuery()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"failed to parse sql: %q %v\", tc.sql, err)\n\t\t\t\t}\n\n\t\t\t\tit, err := db.Query(ctx, tx, stmt, tc.params)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Query failed: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tvar rows [][]interface{}\n\t\t\t\terr = it.Do(func(row []interface{}) error {\n\t\t\t\t\trows = append(rows, row)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t\t\t\t}\n\n\t\t\t\ttc.assert(t, rows)\n\t\t\t})\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/database_test.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudspannerecosystem/memefish\"\n\t\"github.com/cloudspannerecosystem/memefish/ast\"\n\t\"github.com/cloudspannerecosystem/memefish/token\"\n\tcmp \"github.com/google/go-cmp/cmp\"\n\t\"google.golang.org/genproto/googleapis/rpc/errdetails\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/testing/protocmp\"\n\tstructpb \"google.golang.org/protobuf/types/known/structpb\"\n)\n\nvar (\n\tallSchema    = []string{schemaSimple, schemaInterleaved, schemaInterleavedCascade, schemaInterleavedNoAction, schemaForeignCascade, schemaForeignNoAction, schemaCompositePrimaryKeys, schemaFullTypes, schemaArrayTypes, schemaJoinA, schemaJoinB, schemaFromTable, schemaGeneratedValues, schemaGeneratedColumn, schemaDefaultValues, schemaChangeStream}\n\tschemaSimple = `CREATE TABLE Simple (\n  Id INT64 NOT NULL,\n  Value STRING(MAX) NOT NULL,\n) PRIMARY KEY(Id);\n`\n\tschemaInterleaved = `CREATE TABLE ParentTable (\n  Id INT64 NOT NULL,\n  Value STRING(MAX) NOT NULL,\n) PRIMARY KEY(Id);\n\nCREATE TABLE Interleaved (\n  InterleavedId INT64 NOT NULL,\n  Id INT64 NOT NULL,\n  Value STRING(MAX) NOT NULL,\n) PRIMARY KEY(Id, InterleavedId),\nINTERLEAVE IN PARENT ParentTable;\nCREATE INDEX InterleavedKey ON Interleaved(Id, Value), INTERLEAVE IN ParentTable \n`\n\tschemaInterleavedCascade = `CREATE TABLE ParentTableCascade (\n  Id INT64 NOT NULL,\n  Value STRING(MAX) NOT NULL,\n) PRIMARY KEY(Id);\n\nCREATE TABLE InterleavedCascade (\n  InterleavedId INT64 NOT NULL,\n  Id INT64 NOT NULL,\n  Value STRING(MAX) NOT NULL,\n) PRIMARY KEY(Id, InterleavedId),\nINTERLEAVE IN PARENT ParentTableCascade ON DELETE CASCADE;\n`\n\tschemaInterleavedNoAction = `CREATE TABLE ParentTableNoAction (\n  Id INT64 NOT NULL,\n  Value STRING(MAX) NOT NULL,\n) PRIMARY KEY(Id);\n\nCREATE TABLE InterleavedNoAction (\n  InterleavedId INT64 NOT NULL,\n  Id INT64 NOT NULL,\n  Value STRING(MAX) NOT NULL,\n) PRIMARY KEY(Id, InterleavedId),\nINTERLEAVE IN PARENT ParentTableNoAction ON DELETE NO ACTION;\n`\n\n\tschemaForeignCascade = `CREATE TABLE ForeignParentCascade (\n  Id INT64 NOT NULL,\n  Value STRING(MAX) NOT NULL,\n) PRIMARY KEY(Id);\n\nCREATE TABLE ForeignChildCascade (\n  Id INT64 NOT NULL,\n  ForeignId INT64 NOT NULL,\n  ForeignSecondId INT64 NOT NULL,\n  Value STRING(MAX) NOT NULL,\n  CONSTRAINT FK_Cascade FOREIGN KEY (ForeignId) REFERENCES ForeignParentCascade (Id) ON DELETE CASCADE,\n  FOREIGN KEY (ForeignSecondId) REFERENCES ForeignParentCascade (Id) ON DELETE CASCADE,\n) PRIMARY KEY(Id, ForeignId);\n`\n\n\tschemaForeignNoAction = `CREATE TABLE ForeignParentNoAction (\n  Id INT64 NOT NULL,\n  Value STRING(MAX) NOT NULL,\n) PRIMARY KEY(Id);\n\nCREATE TABLE ForeignChildNoAction (\n  Id INT64 NOT NULL,\n  ForeignId INT64 NOT NULL,\n  ForeignSecondId INT64 NOT NULL,\n  Value STRING(MAX) NOT NULL,\n  CONSTRAINT FK_NoAction FOREIGN KEY (ForeignId) REFERENCES ForeignParentNoAction (Id),\n  FOREIGN KEY (ForeignSecondId) REFERENCES ForeignParentNoAction (Id),\n) PRIMARY KEY(Id, ForeignId);\n`\n\n\tschemaCompositePrimaryKeys = `CREATE TABLE CompositePrimaryKeys (\n  Id INT64 NOT NULL,\n  PKey1 STRING(32) NOT NULL,\n  PKey2 INT64 NOT NULL,\n  Error INT64 NOT NULL,\n  X STRING(32) NOT NULL,\n  Y STRING(32) NOT NULL,\n  Z STRING(32) NOT NULL,\n) PRIMARY KEY(PKey1, PKey2 DESC);\nCREATE INDEX CompositePrimaryKeysByXY ON CompositePrimaryKeys(X, Y DESC) STORING (Z);\nCREATE INDEX CompositePrimaryKeysByError ON CompositePrimaryKeys(Error);\n`\n\tschemaFullTypes = `CREATE TABLE FullTypes (\n  PKey STRING(32) NOT NULL,\n  FTString STRING(32) NOT NULL,\n  FTStringNull STRING(32),\n  FTBool BOOL NOT NULL,\n  FTBoolNull BOOL,\n  FTBytes BYTES(32) NOT NULL,\n  FTBytesNull BYTES(32),\n  FTTimestamp TIMESTAMP NOT NULL,\n  FTTimestampNull TIMESTAMP OPTIONS (allow_commit_timestamp=true),\n  FTInt INT64 NOT NULL,\n  FTIntNull INT64,\n  FTFloat FLOAT64 NOT NULL,\n  FTFloatNull FLOAT64,\n  FTDate DATE NOT NULL,\n  FTDateNull DATE,\n) PRIMARY KEY(PKey);\nCREATE UNIQUE INDEX FullTypesByFTString ON FullTypes(FTString);\nCREATE UNIQUE INDEX FullTypesByIntDate ON FullTypes(FTInt, FTDate);\nCREATE INDEX FullTypesByIntTimestamp ON FullTypes(FTInt, FTTimestamp);\nCREATE INDEX FullTypesByTimestamp ON FullTypes(FTTimestamp);\n`\n\tschemaArrayTypes = `CREATE TABLE ArrayTypes (\n  Id INT64 NOT NULL,\n  ArrayString ARRAY<STRING(32)>,\n  ArrayBool ARRAY<BOOL>,\n  ArrayBytes ARRAY<BYTES(32)>,\n  ArrayTimestamp ARRAY<TIMESTAMP>,\n  ArrayInt ARRAY<INT64>,\n  ArrayFloat ARRAY<FLOAT64>,\n  ArrayDate ARRAY<DATE>,\n) PRIMARY KEY(Id);\n`\n\tschemaJoinA = `CREATE TABLE JoinA (\n  Id INT64 NOT NULL,\n  Value STRING(MAX) NOT NULL,\n) PRIMARY KEY(Id);\n`\n\tschemaJoinB = `CREATE TABLE JoinB (\n  Id INT64 NOT NULL,\n  Value STRING(MAX) NOT NULL,\n) PRIMARY KEY(Id);\n`\n\tschemaFromTable = \"CREATE TABLE `From` (\" +\n\t\t\"`ALL` INT64 NOT NULL,\" +\n\t\t\"`CAST` INT64 NOT NULL, \" +\n\t\t\"`JOIN` INT64 NOT NULL, \" +\n\t\t\") PRIMARY KEY(`ALL`); \\n\" +\n\t\t\"CREATE INDEX `ALL` ON `From`(`ALL`);\"\n\tschemaGeneratedColumn = `CREATE TABLE GeneratedColumn (\n  Id INT64 NOT NULL,\n\tValue STRING(MAX) NOT NULL AS (CAST(Id AS STRING)) STORED,\n) PRIMARY KEY(Id);\n`\n\n\tschemaGeneratedValues = `CREATE TABLE GeneratedValues (\n\t\tId INT64 NOT NULL,\n\t\tValue STRING(32) NOT NULL,\n\t\tN INT64 NOT NULL AS (1) STORED,\n\t\tN2 INT64 NOT NULL AS (1+1) STORED,\n\t\tN3 INT64 NOT NULL AS (N2+1) STORED,\n\t\tX STRING(32) NOT NULL AS (\"Value\") STORED,\n\t\tX2 STRING(32) NOT NULL AS (CONCAT(\"Value\", \"Y\")) STORED,\n\t\t) PRIMARY KEY(Id);\n`\n\n\tschemaDefaultValues = `CREATE TABLE DefaultValues (\n\t\tId INT64 NOT NULL,\n\t\tValue STRING(32) NOT NULL,\n\t\tN INT64 NOT NULL DEFAULT (1),\n\t\tX STRING(32) NOT NULL DEFAULT (\"x\"),\n\t\tY ARRAY<INT64> NOT NULL DEFAULT (ARRAY<INT64>[10, 20]),\n\t\tT1 TIMESTAMP NOT NULL DEFAULT (CURRENT_TIMESTAMP()),\n\t\tT2 TIMESTAMP NOT NULL DEFAULT (current_timestamp()),\n\t\tDate TIMESTAMP NOT NULL DEFAULT (CURRENT_DATE()),\n\t\t) PRIMARY KEY(Id);\n`\n\n\tschemaChangeStream = `CREATE CHANGE STREAM EverythingStream\n  FOR ALL;\n`\n\tcompositePrimaryKeysKeys = []string{\n\t\t\"Id\", \"PKey1\", \"PKey2\", \"Error\", \"X\", \"Y\", \"Z\",\n\t}\n\tfullTypesKeys = []string{\n\t\t\"PKey\",\n\t\t\"FTString\",\n\t\t\"FTStringNull\",\n\t\t\"FTBool\",\n\t\t\"FTBoolNull\",\n\t\t\"FTBytes\",\n\t\t\"FTBytesNull\",\n\t\t\"FTTimestamp\",\n\t\t\"FTTimestampNull\",\n\t\t\"FTInt\",\n\t\t\"FTIntNull\",\n\t\t\"FTFloat\",\n\t\t\"FTFloatNull\",\n\t\t\"FTDate\",\n\t\t\"FTDateNull\",\n\t}\n\tarrayTypesKeys = []string{\n\t\t\"Id\",\n\t\t\"ArrayString\",\n\t\t\"ArrayBool\",\n\t\t\"ArrayBytes\",\n\t\t\"ArrayTimestamp\",\n\t\t\"ArrayInt\",\n\t\t\"ArrayFloat\",\n\t\t\"ArrayDate\",\n\t}\n\tfromTableKeys = []string{\"ALL\", \"CAST\", \"JOIN\"}\n)\n\nvar initialData = []struct {\n\ttable  string\n\tcols   []string\n\tvalues [][]*structpb.Value\n}{\n\t{\n\t\ttable: \"Simple\",\n\t\tcols:  []string{\"Id\", \"Value\"},\n\t\tvalues: [][]*structpb.Value{\n\t\t\t{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\ttable: \"CompositePrimaryKeys\",\n\t\tcols:  compositePrimaryKeysKeys,\n\t\tvalues: [][]*structpb.Value{\n\t\t\t{\n\t\t\t\tmakeStringValue(\"1\"),        // Id INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"pkey1xxx\"), // PKey1 STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"100\"),      // PKey2 INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"2\"),        // Error INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"x1\"),       // X STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"y1\"),       // Y STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"z1\"),       // Z STRING(32) NOT NULL,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\ttable: \"FullTypes\",\n\t\tcols:  fullTypesKeys,\n\t\tvalues: [][]*structpb.Value{\n\t\t\t{\n\t\t\t\tmakeStringValue(\"xxx\"),                            // PKey STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"xxx\"),                            // FTString STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"xxx\"),                            // FTStringNull STRING(32),\n\t\t\t\tmakeBoolValue(true),                               // FTBool BOOL NOT NULL,\n\t\t\t\tmakeBoolValue(true),                               // FTBoolNull BOOL,\n\t\t\t\tmakeStringValue(\"eHl6\"),                           // FTBytes BYTES(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"eHl6\"),                           // FTBytesNull BYTES(32),\n\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestamp TIMESTAMP NOT NULL,\n\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestampNull TIMESTAMP,\n\t\t\t\tmakeStringValue(\"100\"),                            // FTInt INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"100\"),                            // FTIntNull INT64,\n\t\t\t\tmakeNumberValue(0.5),                              // FTFloat FLOAT64 NOT NULL,\n\t\t\t\tmakeNumberValue(0.5),                              // FTFloatNull FLOAT64,\n\t\t\t\tmakeStringValue(\"2012-03-04\"),                     // FTDate DATE NOT NULL,\n\t\t\t\tmakeStringValue(\"2012-03-04\"),                     // FTDateNull DATE,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\ttable: \"FullTypes\",\n\t\tcols:  fullTypesKeys,\n\t\tvalues: [][]*structpb.Value{\n\t\t\t{\n\t\t\t\tmakeStringValue(\"yyy\"),                            // PKey STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"yyy\"),                            // FTString STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"yyy\"),                            // FTStringNull STRING(32),\n\t\t\t\tmakeBoolValue(true),                               // FTBool BOOL NOT NULL,\n\t\t\t\tmakeBoolValue(true),                               // FTBoolNull BOOL,\n\t\t\t\tmakeStringValue(\"eHl6\"),                           // FTBytes BYTES(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"eHl6\"),                           // FTBytesNull BYTES(32),\n\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestamp TIMESTAMP NOT NULL,\n\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestampNull TIMESTAMP,\n\t\t\t\tmakeStringValue(\"101\"),                            // FTInt INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"101\"),                            // FTIntNull INT64,\n\t\t\t\tmakeNumberValue(0.5),                              // FTFloat FLOAT64 NOT NULL,\n\t\t\t\tmakeNumberValue(0.5),                              // FTFloatNull FLOAT64,\n\t\t\t\tmakeStringValue(\"2012-03-04\"),                     // FTDate DATE NOT NULL,\n\t\t\t\tmakeStringValue(\"2012-03-04\"),                     // FTDateNull DATE,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\ttable: \"ArrayTypes\",\n\t\tcols:  arrayTypesKeys,\n\t\tvalues: [][]*structpb.Value{\n\t\t\t{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeBoolValue(true),\n\t\t\t\t\tmakeBoolValue(true),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"eHl6\"),\n\t\t\t\t\tmakeStringValue(\"eHl6\"),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"),\n\t\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"101\"),\n\t\t\t\t\tmakeStringValue(\"101\"),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeNumberValue(0.5),\n\t\t\t\t\tmakeNumberValue(0.5),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"2012-03-04\"),\n\t\t\t\t\tmakeStringValue(\"2012-03-04\"),\n\t\t\t\t)),\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\ttable: \"From\",\n\t\tcols:  fromTableKeys,\n\t\tvalues: [][]*structpb.Value{\n\t\t\t{\n\t\t\t\tmakeStringValue(\"1\"),\n\t\t\t\tmakeStringValue(\"1\"),\n\t\t\t\tmakeStringValue(\"1\"),\n\t\t\t},\n\t\t},\n\t},\n}\n\nfunc testRunInTransaction(t *testing.T, ses *session, fn func(*transaction)) {\n\tt.Helper()\n\n\ttx, err := ses.createTransaction(txReadWrite, false)\n\tif err != nil {\n\t\tt.Fatalf(\"createTransaction failed: %v\", err)\n\t}\n\tdefer tx.Done(TransactionCommited)\n\n\tfn(tx)\n\n\tif err := ses.database.Commit(tx); err != nil {\n\t\tt.Fatalf(\"commit failed: %v\", err)\n\t}\n}\n\nfunc createInitialData(t *testing.T, ctx context.Context, db Database, tx *transaction) {\n\tt.Helper()\n\n\tfor _, d := range initialData {\n\t\tfor _, values := range d.values {\n\t\t\tlistValues := []*structpb.ListValue{\n\t\t\t\t{Values: values},\n\t\t\t}\n\t\t\tif err := db.Insert(ctx, tx, d.table, d.cols, listValues); err != nil {\n\t\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc makeNullValue() *structpb.Value {\n\treturn &structpb.Value{Kind: &structpb.Value_NullValue{}}\n}\n\nfunc makeNumberValue(n float64) *structpb.Value {\n\treturn &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: n}}\n}\n\nfunc makeStringValue(s string) *structpb.Value {\n\treturn &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: s}}\n}\n\nfunc makeBoolValue(b bool) *structpb.Value {\n\treturn &structpb.Value{Kind: &structpb.Value_BoolValue{BoolValue: b}}\n}\n\nfunc makeStructValue(v map[string]*structpb.Value) *structpb.Value {\n\treturn &structpb.Value{Kind: &structpb.Value_StructValue{StructValue: &structpb.Struct{\n\t\tFields: v,\n\t}}}\n}\n\nfunc makeListValue(vs ...*structpb.Value) *structpb.ListValue {\n\treturn &structpb.ListValue{Values: vs}\n}\n\nfunc makeListValueAsValue(v *structpb.ListValue) *structpb.Value {\n\treturn &structpb.Value{Kind: &structpb.Value_ListValue{ListValue: v}}\n}\n\nfunc makeTestValue(v interface{}) Value {\n\tswitch vv := v.(type) {\n\tcase string:\n\t\treturn Value{\n\t\t\tData: v,\n\t\t\tType: ValueType{Code: TCString},\n\t\t}\n\tcase []string:\n\t\treturn Value{\n\t\t\tData: v,\n\t\t\tType: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &ValueType{Code: TCString},\n\t\t\t},\n\t\t}\n\tcase int64:\n\t\treturn Value{\n\t\t\tData: v,\n\t\t\tType: ValueType{Code: TCInt64},\n\t\t}\n\tcase int:\n\t\treturn Value{\n\t\t\tData: int64(vv),\n\t\t\tType: ValueType{Code: TCInt64},\n\t\t}\n\tcase []int64:\n\t\treturn Value{\n\t\t\tData: v,\n\t\t\tType: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &ValueType{Code: TCInt64},\n\t\t\t},\n\t\t}\n\tcase float64:\n\t\treturn Value{\n\t\t\tData: v,\n\t\t\tType: ValueType{Code: TCFloat64},\n\t\t}\n\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"fix makeTestValue to be able to convert interface{} to Value: %T\", v))\n\t}\n}\n\nfunc makeTestWrappedArray(code TypeCode, vs ...interface{}) interface{} {\n\tswitch code {\n\tcase TCBool:\n\t\tarr := make([]*bool, len(vs))\n\t\tfor i := range vs {\n\t\t\tif vs[i] == nil {\n\t\t\t\tarr[i] = nil\n\t\t\t} else {\n\t\t\t\ts := new(bool)\n\t\t\t\t*s = vs[i].(bool)\n\t\t\t\tarr[i] = s\n\t\t\t}\n\t\t}\n\t\treturn &ArrayValueEncoder{Values: arr}\n\tcase TCString:\n\t\tarr := make([]*string, len(vs))\n\t\tfor i := range vs {\n\t\t\tif vs[i] == nil {\n\t\t\t\tarr[i] = nil\n\t\t\t} else {\n\t\t\t\ts := new(string)\n\t\t\t\t*s = vs[i].(string)\n\t\t\t\tarr[i] = s\n\t\t\t}\n\t\t}\n\t\treturn &ArrayValueEncoder{Values: arr}\n\tcase TCInt64:\n\t\tarr := make([]*int64, len(vs))\n\t\tfor i := range vs {\n\t\t\tif vs[i] == nil {\n\t\t\t\tarr[i] = nil\n\t\t\t} else {\n\t\t\t\ts := new(int64)\n\t\t\t\tvv, ok := vs[i].(int64)\n\t\t\t\tif !ok {\n\t\t\t\t\tvv = int64(vs[i].(int))\n\t\t\t\t}\n\t\t\t\t*s = vv\n\t\t\t\tarr[i] = s\n\t\t\t}\n\t\t}\n\t\treturn &ArrayValueEncoder{Values: arr}\n\tcase TCFloat64:\n\t\tarr := make([]*float64, len(vs))\n\t\tfor i := range vs {\n\t\t\tif vs[i] == nil {\n\t\t\t\tarr[i] = nil\n\t\t\t} else {\n\t\t\t\ts := new(float64)\n\t\t\t\t*s = vs[i].(float64)\n\t\t\t\tarr[i] = s\n\t\t\t}\n\t\t}\n\t\treturn &ArrayValueEncoder{Values: arr}\n\tcase TCBytes:\n\t\tarr := make([][]byte, len(vs))\n\t\tfor i := range vs {\n\t\t\tif vs[i] == nil {\n\t\t\t\tarr[i] = nil\n\t\t\t} else {\n\t\t\t\tarr[i] = vs[i].([]byte)\n\t\t\t}\n\t\t}\n\t\treturn &ArrayValueEncoder{Values: arr}\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"fix makeTestArray to be able to convert interface{}: %v\", code))\n\t}\n}\n\nfunc makeTestArray(code TypeCode, vs ...interface{}) interface{} {\n\tswitch code {\n\tcase TCBool:\n\t\tarr := make([]*bool, len(vs))\n\t\tfor i := range vs {\n\t\t\tif vs[i] == nil {\n\t\t\t\tarr[i] = nil\n\t\t\t} else {\n\t\t\t\ts := new(bool)\n\t\t\t\t*s = vs[i].(bool)\n\t\t\t\tarr[i] = s\n\t\t\t}\n\t\t}\n\t\treturn arr\n\tcase TCString:\n\t\tarr := make([]*string, len(vs))\n\t\tfor i := range vs {\n\t\t\tif vs[i] == nil {\n\t\t\t\tarr[i] = nil\n\t\t\t} else {\n\t\t\t\ts := new(string)\n\t\t\t\t*s = vs[i].(string)\n\t\t\t\tarr[i] = s\n\t\t\t}\n\t\t}\n\t\treturn arr\n\tcase TCInt64:\n\t\tarr := make([]*int64, len(vs))\n\t\tfor i := range vs {\n\t\t\tif vs[i] == nil {\n\t\t\t\tarr[i] = nil\n\t\t\t} else {\n\t\t\t\ts := new(int64)\n\t\t\t\tvv, ok := vs[i].(int64)\n\t\t\t\tif !ok {\n\t\t\t\t\tvv = int64(vs[i].(int))\n\t\t\t\t}\n\t\t\t\t*s = vv\n\t\t\t\tarr[i] = s\n\t\t\t}\n\t\t}\n\t\treturn arr\n\tcase TCFloat64:\n\t\tarr := make([]*float64, len(vs))\n\t\tfor i := range vs {\n\t\t\tif vs[i] == nil {\n\t\t\t\tarr[i] = nil\n\t\t\t} else {\n\t\t\t\ts := new(float64)\n\t\t\t\t*s = vs[i].(float64)\n\t\t\t\tarr[i] = s\n\t\t\t}\n\t\t}\n\t\treturn arr\n\tcase TCBytes:\n\t\tarr := make([][]byte, len(vs))\n\t\tfor i := range vs {\n\t\t\tif vs[i] == nil {\n\t\t\t\tarr[i] = nil\n\t\t\t} else {\n\t\t\t\tarr[i] = vs[i].([]byte)\n\t\t\t}\n\t\t}\n\t\treturn arr\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"fix makeTestArray to be able to convert interface{}: %v\", code))\n\t}\n}\n\nfunc parseDDL(t *testing.T, s string) []ast.DDL {\n\tvar ddls []ast.DDL\n\tstmts := strings.Split(s, \";\")\n\tfor _, stmt := range stmts {\n\t\tstmt := strings.TrimSpace(stmt)\n\t\tif stmt == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tddl, err := (&memefish.Parser{\n\t\t\tLexer: &memefish.Lexer{\n\t\t\t\tFile: &token.File{FilePath: \"\", Buffer: stmt},\n\t\t\t},\n\t\t}).ParseDDL()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"parse error: %v\", err)\n\t\t}\n\n\t\tddls = append(ddls, ddl)\n\t}\n\n\treturn ddls\n}\n\nfunc TestApplyDDL(t *testing.T) {\n\ttable := []struct {\n\t\tddl string\n\t}{\n\t\t{\n\t\t\tddl: schemaSimple,\n\t\t},\n\t\t{\n\t\t\tddl: schemaCompositePrimaryKeys,\n\t\t},\n\t\t{\n\t\t\tddl: schemaFullTypes,\n\t\t},\n\t\t{\n\t\t\tddl: schemaGeneratedValues,\n\t\t},\n\t\t{\n\t\t\tddl: schemaDefaultValues,\n\t\t},\n\t\t{\n\t\t\tddl: schemaChangeStream,\n\t\t},\n\t}\n\n\tfor _, tt := range table {\n\t\tctx := context.Background()\n\t\tdb := newDatabase()\n\t\tddls := parseDDL(t, tt.ddl)\n\t\tfor _, ddl := range ddls {\n\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t}\n\t}\n}\n\nfunc TestClose(t *testing.T) {\n\tdb := newDatabase()\n\terr := db.Close()\n\tif err != nil {\n\t\tt.Fatalf(\"failed to close: %v\", err)\n\t}\n\n\terr = db.Close()\n\tif err != nil {\n\t\tt.Fatalf(\"failed to close: %v\", err)\n\t}\n}\n\nfunc TestRead(t *testing.T) {\n\tctx := context.Background()\n\tdb := newDatabase()\n\tfor _, s := range allSchema {\n\t\tddls := parseDDL(t, s)\n\t\tfor _, ddl := range ddls {\n\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t}\n\t}\n\n\tfor _, query := range []string{\n\t\t`INSERT INTO Simple VALUES(100, \"xxx\")`,\n\t\t`INSERT INTO Simple VALUES(200, \"yyy\")`,\n\t\t`INSERT INTO Simple VALUES(300, \"zzz\")`,\n\n\t\t`INSERT INTO CompositePrimaryKeys VALUES(1, \"aaa\", 1, 0, \"x1\", \"y1\", \"z\")`,\n\t\t`INSERT INTO CompositePrimaryKeys VALUES(2, \"bbb\", 2, 0, \"x1\", \"y2\", \"z\")`,\n\t\t`INSERT INTO CompositePrimaryKeys VALUES(3, \"bbb\", 3, 0, \"x1\", \"y3\", \"z\")`,\n\t\t`INSERT INTO CompositePrimaryKeys VALUES(4, \"ccc\", 3, 0, \"x2\", \"y4\", \"z\")`,\n\t\t`INSERT INTO CompositePrimaryKeys VALUES(5, \"ccc\", 4, 0, \"x2\", \"y5\", \"z\")`,\n\n\t\t\"INSERT INTO `From` VALUES(1, 1, 1)\",\n\n\t\t`INSERT INTO GeneratedValues (Id, Value) VALUES(1, \"xxx\")`,\n\t\t`INSERT INTO DefaultValues (Id, Value) VALUES(1, \"xxx\")`,\n\t} {\n\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t}\n\t}\n\n\ttable := map[string]struct {\n\t\ttbl   string\n\t\tidx   string\n\t\tcols  []string\n\t\tks    *KeySet\n\t\tlimit int64\n\n\t\texpected [][]interface{}\n\t}{\n\t\t\"SimpleFull\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\tks:    &KeySet{All: true},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t},\n\t\t},\n\t\t\"SimplePartialColumn\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\tcols:  []string{\"Value\"},\n\t\t\tks:    &KeySet{All: true},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"xxx\"},\n\t\t\t\t[]interface{}{\"yyy\"},\n\t\t\t\t[]interface{}{\"zzz\"},\n\t\t\t},\n\t\t},\n\t\t\"SimpleColumnOrder\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\tcols:  []string{\"Value\", \"Id\"},\n\t\t\tks:    &KeySet{All: true},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"xxx\", int64(100)},\n\t\t\t\t[]interface{}{\"yyy\", int64(200)},\n\t\t\t\t[]interface{}{\"zzz\", int64(300)},\n\t\t\t},\n\t\t},\n\t\t\"SimpleLimit\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\tks:    &KeySet{All: true},\n\t\t\tlimit: 2,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t},\n\t\t},\n\t\t\"SimpleDuplicateColumns\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\tcols:  []string{\"Id\", \"Value\", \"Id\", \"Value\"},\n\t\t\tks:    &KeySet{All: true},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\", int64(100), \"xxx\"},\n\t\t\t\t[]interface{}{int64(200), \"yyy\", int64(200), \"yyy\"},\n\t\t\t\t[]interface{}{int64(300), \"zzz\", int64(300), \"zzz\"},\n\t\t\t},\n\t\t},\n\n\t\t// Simple KeySet\n\t\t\"Simple_SingleKey\": {\n\t\t\ttbl:  \"Simple\",\n\t\t\tcols: []string{\"Id\", \"Value\"},\n\t\t\tks: &KeySet{\n\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\tmakeListValue(makeStringValue(\"100\")),\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_SingleKey_NotFound\": {\n\t\t\ttbl:  \"Simple\",\n\t\t\tcols: []string{\"Id\", \"Value\"},\n\t\t\tks: &KeySet{\n\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\tmakeListValue(makeStringValue(\"1000\")),\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit:    100,\n\t\t\texpected: nil,\n\t\t},\n\t\t\"Simple_MultiKeys\": {\n\t\t\ttbl:  \"Simple\",\n\t\t\tcols: []string{\"Id\", \"Value\"},\n\t\t\tks: &KeySet{\n\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\tmakeListValue(makeStringValue(\"100\")),\n\t\t\t\t\tmakeListValue(makeStringValue(\"300\")),\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_MultiKeys_PartialNotFound\": {\n\t\t\ttbl:  \"Simple\",\n\t\t\tcols: []string{\"Id\", \"Value\"},\n\t\t\tks: &KeySet{\n\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\tmakeListValue(makeStringValue(\"100\")),\n\t\t\t\t\tmakeListValue(makeStringValue(\"300\")),\n\t\t\t\t\tmakeListValue(makeStringValue(\"1000\")),\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_KeyRange\": {\n\t\t\ttbl:  \"Simple\",\n\t\t\tcols: []string{\"Id\", \"Value\"},\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"100\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"300\")),\n\t\t\t\t\t\tstartClosed: true,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_KeyRange2\": {\n\t\t\ttbl:  \"Simple\",\n\t\t\tcols: []string{\"Id\", \"Value\"},\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"150\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"250\")),\n\t\t\t\t\t\tstartClosed: true,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_KeyRange_OpenClose\": {\n\t\t\ttbl:  \"Simple\",\n\t\t\tcols: []string{\"Id\", \"Value\"},\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"100\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"300\")),\n\t\t\t\t\t\tstartClosed: false,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t\t[]interface{}{int64(300), \"zzz\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_KeyRange_OpenClose2\": {\n\t\t\ttbl:  \"CompositePrimaryKeys\",\n\t\t\tcols: []string{\"Id\", \"PKey1\", \"PKey2\"},\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"bbb\"), makeStringValue(\"3\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"ccc\"), makeStringValue(\"3\")),\n\t\t\t\t\t\tstartClosed: false,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(4), \"ccc\", int64(3)},\n\t\t\t},\n\t\t},\n\t\t\"Simple_KeyRange_CloseOpen\": {\n\t\t\ttbl:  \"Simple\",\n\t\t\tcols: []string{\"Id\", \"Value\"},\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"100\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"300\")),\n\t\t\t\t\t\tstartClosed: true,\n\t\t\t\t\t\tendClosed:   false,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_KeyRange_OpenOpen\": {\n\t\t\ttbl:  \"Simple\",\n\t\t\tcols: []string{\"Id\", \"Value\"},\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"100\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"300\")),\n\t\t\t\t\t\tstartClosed: false,\n\t\t\t\t\t\tendClosed:   false,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(200), \"yyy\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_KeyRange_LessPrimaryKey\": {\n\t\t\ttbl:  \"CompositePrimaryKeys\",\n\t\t\tcols: []string{\"Id\", \"PKey1\", \"PKey2\"},\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"bbb\"), makeStringValue(\"2\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"bbb\")),\n\t\t\t\t\t\tstartClosed: true,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(2), \"bbb\", int64(2)},\n\t\t\t},\n\t\t},\n\t\t\"Simple_KeyRange_LessPrimaryKey2\": {\n\t\t\ttbl:  \"CompositePrimaryKeys\",\n\t\t\tcols: []string{\"Id\", \"PKey1\", \"PKey2\"},\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"bbb\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"bbb\"), makeStringValue(\"3\")),\n\t\t\t\t\t\tstartClosed: true,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(3), \"bbb\", int64(3)},\n\t\t\t},\n\t\t},\n\t\t\"Simple_KeyRange_LessPrimaryKey3\": {\n\t\t\ttbl:  \"CompositePrimaryKeys\",\n\t\t\tcols: []string{\"Id\", \"PKey1\", \"PKey2\"},\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"bbb\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"bbb\")),\n\t\t\t\t\t\tstartClosed: true,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(3), \"bbb\", int64(3)},\n\t\t\t\t[]interface{}{int64(2), \"bbb\", int64(2)},\n\t\t\t},\n\t\t},\n\t\t\"Simple_KeyRange_LessPrimaryKeyOpenClosed\": {\n\t\t\ttbl:  \"CompositePrimaryKeys\",\n\t\t\tcols: []string{\"Id\", \"PKey1\", \"PKey2\"},\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"bbb\"), makeStringValue(\"2\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"bbb\")),\n\t\t\t\t\t\tstartClosed: false,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit:    100,\n\t\t\texpected: nil,\n\t\t},\n\t\t\"Simple_KeyRange_LessPrimaryKeyClosedOpen\": {\n\t\t\ttbl:  \"CompositePrimaryKeys\",\n\t\t\tcols: []string{\"Id\", \"PKey1\", \"PKey2\"},\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"bbb\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"bbb\"), makeStringValue(\"3\")),\n\t\t\t\t\t\tstartClosed: true,\n\t\t\t\t\t\tendClosed:   false,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit:    100,\n\t\t\texpected: nil,\n\t\t},\n\n\t\t// Composite Keys\n\t\t\"CompositePrimaryKeys_Keys\": {\n\t\t\ttbl:  \"CompositePrimaryKeys\",\n\t\t\tcols: []string{\"Id\", \"PKey1\", \"PKey2\"},\n\t\t\tks: &KeySet{\n\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\tmakeListValue(makeStringValue(\"aaa\"), makeStringValue(\"1\")),\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(1), \"aaa\", int64(1)},\n\t\t\t},\n\t\t},\n\t\t\"CompositePrimaryKeys_Keys_Multi\": {\n\t\t\ttbl:  \"CompositePrimaryKeys\",\n\t\t\tcols: []string{\"Id\", \"PKey1\", \"PKey2\"},\n\t\t\tks: &KeySet{\n\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\tmakeListValue(makeStringValue(\"aaa\"), makeStringValue(\"100\")),\n\t\t\t\t\tmakeListValue(makeStringValue(\"xxx\"), makeStringValue(\"2\")),\n\t\t\t\t\tmakeListValue(makeStringValue(\"ccc\"), makeStringValue(\"3\")),\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(4), \"ccc\", int64(3)},\n\t\t\t},\n\t\t},\n\n\t\t// Composite Ranges\n\t\t\"CompositePrimaryKeys_Ranges\": {\n\t\t\ttbl:  \"CompositePrimaryKeys\",\n\t\t\tcols: []string{\"Id\", \"PKey1\", \"PKey2\"},\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"bbb\"), makeStringValue(\"3\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"ccc\"), makeStringValue(\"3\")),\n\t\t\t\t\t\tstartClosed: true,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(3), \"bbb\", int64(3)},\n\t\t\t\t[]interface{}{int64(4), \"ccc\", int64(3)},\n\t\t\t},\n\t\t},\n\t\t\"CompositePrimaryKeys_Ranges_Multi\": {\n\t\t\ttbl:  \"CompositePrimaryKeys\",\n\t\t\tcols: []string{\"Id\", \"PKey1\", \"PKey2\"},\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"bbb\"), makeStringValue(\"3\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"bbb\"), makeStringValue(\"2\")),\n\t\t\t\t\t\tstartClosed: true,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"ccc\"), makeStringValue(\"4\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"ccc\"), makeStringValue(\"3\")),\n\t\t\t\t\t\tstartClosed: true,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(3), \"bbb\", int64(3)},\n\t\t\t\t[]interface{}{int64(2), \"bbb\", int64(2)},\n\t\t\t\t[]interface{}{int64(5), \"ccc\", int64(4)},\n\t\t\t\t[]interface{}{int64(4), \"ccc\", int64(3)},\n\t\t\t},\n\t\t},\n\n\t\t// Composite Keys and Ranges\n\t\t\"CompositePrimaryKeys_KeysRanges\": {\n\t\t\ttbl:  \"CompositePrimaryKeys\",\n\t\t\tcols: []string{\"Id\", \"PKey1\", \"PKey2\"},\n\t\t\tks: &KeySet{\n\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\tmakeListValue(makeStringValue(\"aaa\"), makeStringValue(\"1\")),\n\t\t\t\t},\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"bbb\"), makeStringValue(\"3\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"bbb\"), makeStringValue(\"2\")),\n\t\t\t\t\t\tstartClosed: true,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(1), \"aaa\", int64(1)},\n\t\t\t\t[]interface{}{int64(3), \"bbb\", int64(3)},\n\t\t\t\t[]interface{}{int64(2), \"bbb\", int64(2)},\n\t\t\t},\n\t\t},\n\n\t\t// Composite SecondaryIndex\n\t\t\"CompositePrimaryKeys_Index\": {\n\t\t\ttbl:   \"CompositePrimaryKeys\",\n\t\t\tidx:   \"CompositePrimaryKeysByXY\",\n\t\t\tcols:  []string{\"PKey1\", \"PKey2\", \"X\", \"Y\", \"Z\"},\n\t\t\tks:    &KeySet{All: true},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"bbb\", int64(3), \"x1\", \"y3\", \"z\"},\n\t\t\t\t[]interface{}{\"bbb\", int64(2), \"x1\", \"y2\", \"z\"},\n\t\t\t\t[]interface{}{\"aaa\", int64(1), \"x1\", \"y1\", \"z\"},\n\t\t\t\t[]interface{}{\"ccc\", int64(4), \"x2\", \"y5\", \"z\"},\n\t\t\t\t[]interface{}{\"ccc\", int64(3), \"x2\", \"y4\", \"z\"},\n\t\t\t},\n\t\t},\n\n\t\t// Escape Keywork\n\t\t\"Keyword_All\": {\n\t\t\ttbl:   \"From\",\n\t\t\tidx:   \"\",\n\t\t\tcols:  fromTableKeys,\n\t\t\tks:    &KeySet{All: true},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(1), int64(1), int64(1)},\n\t\t\t},\n\t\t},\n\t\t\"Keyword_Keys\": {\n\t\t\ttbl:  \"From\",\n\t\t\tidx:  \"\",\n\t\t\tcols: fromTableKeys,\n\t\t\tks: &KeySet{\n\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\tmakeListValue(makeStringValue(\"1\")),\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(1), int64(1), int64(1)},\n\t\t\t},\n\t\t},\n\t\t\"Keyword_Ranges\": {\n\t\t\ttbl:  \"From\",\n\t\t\tidx:  \"\",\n\t\t\tcols: fromTableKeys,\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"1\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"1\")),\n\t\t\t\t\t\tstartClosed: true,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(1), int64(1), int64(1)},\n\t\t\t},\n\t\t},\n\t\t\"Keyword_Index\": {\n\t\t\ttbl:   \"From\",\n\t\t\tidx:   \"ALL\",\n\t\t\tcols:  []string{\"ALL\"},\n\t\t\tks:    &KeySet{All: true},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(1)},\n\t\t\t},\n\t\t},\n\t\t\"GeneratedValues_Full\": {\n\t\t\ttbl:   \"GeneratedValues\",\n\t\t\tcols:  []string{\"Id\", \"Value\", \"N\", \"N2\", \"N3\", \"X\", \"X2\"},\n\t\t\tks:    &KeySet{All: true},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(1), \"xxx\", int64(1), int64(2), int64(3), \"xxx\", \"xxxY\"},\n\t\t\t},\n\t\t},\n\t\t\"DefaultValues_Full\": {\n\t\t\ttbl:   \"DefaultValues\",\n\t\t\tcols:  []string{\"Id\", \"Value\", \"N\", \"X\", \"Y\"},\n\t\t\tks:    &KeySet{All: true},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(1), \"xxx\", int64(1), \"x\", []*int64{}},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tt := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\t\t\tses := newSession(db, \"foo\")\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tit, err := db.Read(ctx, tx, tt.tbl, tt.idx, tt.cols, tt.ks, tt.limit)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Read failed: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tvar rows [][]interface{}\n\t\t\t\terr = it.Do(func(row []interface{}) error {\n\t\t\t\t\trows = append(rows, row)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tif diff := cmp.Diff(tt.expected, rows); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestReadError(t *testing.T) {\n\tctx := context.Background()\n\tdb := newDatabase()\n\tfor _, s := range allSchema {\n\t\tddls := parseDDL(t, s)\n\t\tfor _, ddl := range ddls {\n\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t}\n\t}\n\n\ttable := map[string]struct {\n\t\ttbl     string\n\t\tidx     string\n\t\tcols    []string\n\t\tks      *KeySet\n\t\tlimit   int64\n\t\tcode    codes.Code\n\t\tmsg     *regexp.Regexp\n\t\tdetails []interface{}\n\t}{\n\t\t\"EmptyColumns\": {\n\t\t\ttbl:     \"Simple\",\n\t\t\tidx:     \"\",\n\t\t\tcols:    []string{},\n\t\t\tks:      &KeySet{All: true},\n\t\t\tlimit:   100,\n\t\t\tcode:    codes.InvalidArgument,\n\t\t\tmsg:     regexp.MustCompile(`Invalid StreamingRead request`),\n\t\t\tdetails: []interface{}{},\n\t\t},\n\t\t\"EmptyTableName\": {\n\t\t\ttbl:     \"\",\n\t\t\tidx:     \"\",\n\t\t\tcols:    []string{\"Id\"},\n\t\t\tks:      &KeySet{All: true},\n\t\t\tlimit:   100,\n\t\t\tcode:    codes.InvalidArgument,\n\t\t\tmsg:     regexp.MustCompile(`Invalid StreamingRead request`),\n\t\t\tdetails: []interface{}{},\n\t\t},\n\t\t\"EmptyKeySet\": {\n\t\t\ttbl:     \"Simple\",\n\t\t\tidx:     \"\",\n\t\t\tcols:    []string{\"Id\"},\n\t\t\tks:      nil,\n\t\t\tlimit:   100,\n\t\t\tcode:    codes.InvalidArgument,\n\t\t\tmsg:     regexp.MustCompile(`Invalid StreamingRead request`),\n\t\t\tdetails: []interface{}{},\n\t\t},\n\n\t\t\"TableNotFound\": {\n\t\t\ttbl:   \"NotExistTable\",\n\t\t\tidx:   \"\",\n\t\t\tcols:  []string{\"Id\"},\n\t\t\tks:    &KeySet{All: true},\n\t\t\tlimit: 100,\n\t\t\tcode:  codes.NotFound,\n\t\t\tmsg:   regexp.MustCompile(`Table not found`),\n\t\t\tdetails: []interface{}{\n\t\t\t\t&errdetails.ResourceInfo{\n\t\t\t\t\tResourceType: \"spanner.googleapis.com/Table\",\n\t\t\t\t\tResourceName: \"NotExistTable\",\n\t\t\t\t\tDescription:  \"Table not found\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"IndexNotFound\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\tidx:   \"NotExistIndex\",\n\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\tks:    &KeySet{All: true},\n\t\t\tlimit: 100,\n\t\t\tcode:  codes.NotFound,\n\t\t\tmsg:   regexp.MustCompile(`Index not found on table`),\n\t\t\tdetails: []interface{}{\n\t\t\t\t&errdetails.ResourceInfo{\n\t\t\t\t\tResourceType: \"spanner.googleapis.com/Index\",\n\t\t\t\t\tResourceName: \"NotExistIndex\",\n\t\t\t\t\tDescription:  \"Index not found on table Simple\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ColumnNotFound\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\tidx:   \"\",\n\t\t\tcols:  []string{\"Id\", \"Xyz\"},\n\t\t\tks:    &KeySet{All: true},\n\t\t\tlimit: 100,\n\t\t\tcode:  codes.NotFound,\n\t\t\tmsg:   regexp.MustCompile(`Column not found`),\n\t\t\tdetails: []interface{}{\n\t\t\t\t&errdetails.ResourceInfo{\n\t\t\t\t\tResourceType: \"spanner.googleapis.com/Column\",\n\t\t\t\t\tResourceName: \"Xyz\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ColumnNotFoundOnSecondaryIndex\": {\n\t\t\ttbl:     \"CompositePrimaryKeys\",\n\t\t\tidx:     \"CompositePrimaryKeysByXY\",\n\t\t\tcols:    []string{\"Id\", \"PKey1\", \"PKey2\", \"X\", \"Y\", \"Z\"},\n\t\t\tks:      &KeySet{All: true},\n\t\t\tlimit:   100,\n\t\t\tcode:    codes.Unimplemented, // real spanner returns Unimplemented\n\t\t\tmsg:     regexp.MustCompile(`Reading non-indexed columns using an index is not supported. Consider adding Id to the index using a STORING clause`),\n\t\t\tdetails: []interface{}{},\n\t\t},\n\t}\n\n\tfor name, tt := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\t\t\tses := newSession(db, \"foo\")\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\t_, err := db.Read(ctx, tx, tt.tbl, tt.idx, tt.cols, tt.ks, tt.limit)\n\t\t\t\tst := status.Convert(err)\n\t\t\t\tif st.Code() != tt.code {\n\t\t\t\t\tt.Errorf(\"expect code to be %v but got %v\", tt.code, st.Code())\n\t\t\t\t}\n\t\t\t\tif !tt.msg.MatchString(st.Message()) {\n\t\t\t\t\tt.Errorf(\"unexpected error message: %v\", st.Message())\n\t\t\t\t}\n\t\t\t\tif diff := cmp.Diff(tt.details, st.Details(), protocmp.Transform()); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestRead_FullType_Range(t *testing.T) {\n\tctx := context.Background()\n\tdb := newDatabase()\n\n\tschema := []string{\n\t\t`CREATE TABLE FTString ( Id STRING(MAX) NOT NULL ) PRIMARY KEY (Id)`,\n\t\t`CREATE TABLE FTTimestamp ( Id TIMESTAMP NOT NULL ) PRIMARY KEY (Id)`,\n\t}\n\tfor _, s := range schema {\n\t\tddls := parseDDL(t, s)\n\t\tfor _, ddl := range ddls {\n\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t}\n\t}\n\n\tfor _, query := range []string{\n\t\t`INSERT INTO FTString VALUES(\"aaa\")`,\n\t\t`INSERT INTO FTString VALUES(\"aaaa\")`,\n\t\t`INSERT INTO FTString VALUES(\"aaab\")`,\n\t\t`INSERT INTO FTString VALUES(\"11\")`,\n\t\t`INSERT INTO FTString VALUES(\"100\")`,\n\n\t\t`INSERT INTO FTTimestamp VALUES(\"2012-03-04T12:34:56.123456789Z\")`,\n\t\t`INSERT INTO FTTimestamp VALUES(\"2012-03-04T00:00:00.123456789Z\")`,\n\t\t`INSERT INTO FTTimestamp VALUES(\"2012-03-04T00:00:00.999999999Z\")`,\n\t\t`INSERT INTO FTTimestamp VALUES(\"2012-03-05T00:00:00.000000000Z\")`,\n\t} {\n\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t}\n\t}\n\n\ttable := map[string]struct {\n\t\ttbl         string\n\t\tcols        []string\n\t\tstart       *structpb.ListValue\n\t\tend         *structpb.ListValue\n\t\tstartClosed bool\n\t\tendClosed   bool\n\n\t\texpected [][]interface{}\n\t}{\n\t\t\"FTString_1\": {\n\t\t\ttbl:         \"FTString\",\n\t\t\tcols:        []string{\"Id\"},\n\t\t\tstart:       makeListValue(makeStringValue(\"aaa\")),\n\t\t\tend:         makeListValue(makeStringValue(\"aaz\")),\n\t\t\tstartClosed: true, endClosed: true,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"aaa\"},\n\t\t\t\t[]interface{}{\"aaaa\"},\n\t\t\t\t[]interface{}{\"aaab\"},\n\t\t\t},\n\t\t},\n\t\t\"FTString_2\": {\n\t\t\ttbl:         \"FTString\",\n\t\t\tcols:        []string{\"Id\"},\n\t\t\tstart:       makeListValue(makeStringValue(\"10\")),\n\t\t\tend:         makeListValue(makeStringValue(\"11\")),\n\t\t\tstartClosed: true, endClosed: true,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"100\"},\n\t\t\t\t[]interface{}{\"11\"},\n\t\t\t},\n\t\t},\n\t\t\"FTTimestamp_1\": {\n\t\t\ttbl:         \"FTTimestamp\",\n\t\t\tcols:        []string{\"Id\"},\n\t\t\tstart:       makeListValue(makeStringValue(\"2012-03-04T00:00:00.000000000Z\")),\n\t\t\tend:         makeListValue(makeStringValue(\"2012-03-05T00:00:00.000000000Z\")),\n\t\t\tstartClosed: true, endClosed: false,\n\t\t\texpected: [][]interface{}{\n\n\t\t\t\t[]interface{}{\"2012-03-04T00:00:00.123456789Z\"},\n\t\t\t\t[]interface{}{\"2012-03-04T00:00:00.999999999Z\"},\n\t\t\t\t[]interface{}{\"2012-03-04T12:34:56.123456789Z\"},\n\t\t\t},\n\t\t},\n\t\t\"FTTimestamp_2\": {\n\t\t\ttbl:         \"FTTimestamp\",\n\t\t\tcols:        []string{\"Id\"},\n\t\t\tstart:       makeListValue(makeStringValue(\"2012-03-04T00:00:00.000000000Z\")),\n\t\t\tend:         makeListValue(makeStringValue(\"2012-03-04T00:00:01.000000000Z\")),\n\t\t\tstartClosed: true, endClosed: false,\n\t\t\texpected: [][]interface{}{\n\n\t\t\t\t[]interface{}{\"2012-03-04T00:00:00.123456789Z\"},\n\t\t\t\t[]interface{}{\"2012-03-04T00:00:00.999999999Z\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tt := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\t\t\tses := newSession(db, \"foo\")\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tks := &KeySet{\n\t\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstart:       tt.start,\n\t\t\t\t\t\t\tend:         tt.end,\n\t\t\t\t\t\t\tstartClosed: tt.startClosed,\n\t\t\t\t\t\t\tendClosed:   tt.endClosed,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tit, err := db.Read(ctx, tx, tt.tbl, \"\", tt.cols, ks, 100)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Read failed: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tvar rows [][]interface{}\n\t\t\t\terr = it.Do(func(row []interface{}) error {\n\t\t\t\t\trows = append(rows, row)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tif diff := cmp.Diff(tt.expected, rows); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestInsertAndReplace(t *testing.T) {\n\ttable := map[string]struct {\n\t\ttbl    string\n\t\twcols  []string\n\t\tvalues []*structpb.Value\n\t\tcols   []string\n\t\tlimit  int64\n\n\t\texpected [][]interface{}\n\t}{\n\t\t\"Simple\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t},\n\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t},\n\t\t},\n\t\t\"SimpleMaxInt\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(strconv.FormatInt(0x7FFFFFFFFFFFFFFF, 10)),\n\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t},\n\t\t\tcols:  []string{\"Id\"},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(0x7FFFFFFFFFFFFFFF)},\n\t\t\t},\n\t\t},\n\t\t\"SimpleMinInt\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(strconv.FormatInt(-0x7FFFFFFFFFFFFFFF, 10)),\n\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t},\n\t\t\tcols:  []string{\"Id\"},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(-0x7FFFFFFFFFFFFFFF)},\n\t\t\t},\n\t\t},\n\t\t\"FullType\": {\n\t\t\ttbl:   \"FullTypes\",\n\t\t\twcols: fullTypesKeys,\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"xxx\"),                            // PKey STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"xxx\"),                            // FTString STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"xxx\"),                            // FTStringNull STRING(32),\n\t\t\t\tmakeBoolValue(true),                               // FTBool BOOL NOT NULL,\n\t\t\t\tmakeBoolValue(true),                               // FTBoolNull BOOL,\n\t\t\t\tmakeStringValue(\"eHl6\"),                           // FTBytes BYTES(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"eHl6\"),                           // FTBytesNull BYTES(32),\n\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestamp TIMESTAMP NOT NULL,\n\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestampNull TIMESTAMP,\n\t\t\t\tmakeStringValue(\"100\"),                            // FTInt INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"100\"),                            // FTIntNull INT64,\n\t\t\t\tmakeNumberValue(0.5),                              // FTFloat FLOAT64 NOT NULL,\n\t\t\t\tmakeNumberValue(0.5),                              // FTFloatNull FLOAT64,\n\t\t\t\tmakeStringValue(\"2012-03-04\"),                     // FTDate DATE NOT NULL,\n\t\t\t\tmakeStringValue(\"2012-03-04\"),                     // FTDateNull DATE,\n\t\t\t},\n\t\t\tcols: []string{\n\t\t\t\t\"PKey\",\n\t\t\t\t\"FTString\", \"FTStringNull\",\n\t\t\t\t\"FTBool\", \"FTBoolNull\",\n\t\t\t\t\"FTBytes\", \"FTBytesNull\",\n\t\t\t\t\"FTTimestamp\", \"FTTimestampNull\",\n\t\t\t\t\"FTInt\", \"FTIntNull\",\n\t\t\t\t\"FTFloat\", \"FTFloatNull\",\n\t\t\t\t\"FTDate\", \"FTDateNull\",\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"xxx\",\n\t\t\t\t\t\"xxx\", \"xxx\",\n\t\t\t\t\ttrue, true,\n\t\t\t\t\t[]byte(\"xyz\"), []byte(\"xyz\"),\n\t\t\t\t\t\"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.123456789Z\",\n\t\t\t\t\tint64(100), int64(100),\n\t\t\t\t\tfloat64(0.5), float64(0.5),\n\t\t\t\t\t\"2012-03-04\", \"2012-03-04\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"FullType_WithNull\": {\n\t\t\ttbl:   \"FullTypes\",\n\t\t\twcols: fullTypesKeys,\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"xxx\"),  // PKey STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"xxx\"),  // FTString STRING(32) NOT NULL,\n\t\t\t\tmakeNullValue(),         // FTStringNull STRING(32),\n\t\t\t\tmakeBoolValue(true),     // FTBool BOOL NOT NULL,\n\t\t\t\tmakeNullValue(),         // FTBoolNull BOOL,\n\t\t\t\tmakeStringValue(\"eHl6\"), // FTBytes BYTES(32) NOT NULL,\n\t\t\t\tmakeNullValue(),         // FTBytesNull BYTES(32),\n\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestamp TIMESTAMP NOT NULL,\n\t\t\t\tmakeNullValue(),               // FTTimestampNull TIMESTAMP,\n\t\t\t\tmakeStringValue(\"100\"),        // FTInt INT64 NOT NULL,\n\t\t\t\tmakeNullValue(),               // FTIntNull INT64,\n\t\t\t\tmakeNumberValue(0.5),          // FTFloat FLOAT64 NOT NULL,\n\t\t\t\tmakeNullValue(),               // FTFloatNull FLOAT64,\n\t\t\t\tmakeStringValue(\"2012-03-04\"), // FTDate DATE NOT NULL,\n\t\t\t\tmakeNullValue(),               // FTDateNull DATE,\n\t\t\t},\n\t\t\tcols: []string{\n\t\t\t\t\"PKey\",\n\t\t\t\t\"FTString\", \"FTStringNull\",\n\t\t\t\t\"FTBool\", \"FTBoolNull\",\n\t\t\t\t\"FTBytes\", \"FTBytesNull\",\n\t\t\t\t\"FTTimestamp\", \"FTTimestampNull\",\n\t\t\t\t\"FTInt\", \"FTIntNull\",\n\t\t\t\t\"FTFloat\", \"FTFloatNull\",\n\t\t\t\t\"FTDate\", \"FTDateNull\",\n\t\t\t},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"xxx\",\n\t\t\t\t\t\"xxx\", nil,\n\t\t\t\t\ttrue, nil,\n\t\t\t\t\t[]byte(\"xyz\"), nil,\n\t\t\t\t\t\"2012-03-04T12:34:56.123456789Z\", nil,\n\t\t\t\t\tint64(100), nil,\n\t\t\t\t\tfloat64(0.5), nil,\n\t\t\t\t\t\"2012-03-04\", nil,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"FullType_NoValuesForNull\": {\n\t\t\ttbl: \"FullTypes\",\n\t\t\twcols: []string{\n\t\t\t\t\"PKey\",\n\t\t\t\t\"FTString\",\n\t\t\t\t\"FTBool\",\n\t\t\t\t\"FTBytes\",\n\t\t\t\t\"FTTimestamp\",\n\t\t\t\t\"FTInt\",\n\t\t\t\t\"FTFloat\",\n\t\t\t\t\"FTDate\",\n\t\t\t},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"xxx\"),                            // PKey STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"xxx\"),                            // FTString STRING(32) NOT NULL,\n\t\t\t\tmakeBoolValue(true),                               // FTBool BOOL NOT NULL,\n\t\t\t\tmakeStringValue(\"eHl6\"),                           // FTBytes BYTES(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestamp TIMESTAMP NOT NULL,\n\t\t\t\tmakeStringValue(\"100\"),                            // FTInt INT64 NOT NULL,\n\t\t\t\tmakeNumberValue(0.5),                              // FTFloat FLOAT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"2012-03-04\"),                     // FTDate DATE NOT NULL,\n\t\t\t},\n\t\t\tcols:  fullTypesKeys,\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"xxx\",\n\t\t\t\t\t\"xxx\", nil,\n\t\t\t\t\ttrue, nil,\n\t\t\t\t\t[]byte(\"xyz\"), nil,\n\t\t\t\t\t\"2012-03-04T12:34:56.123456789Z\", nil,\n\t\t\t\t\tint64(100), nil,\n\t\t\t\t\tfloat64(0.5), nil,\n\t\t\t\t\t\"2012-03-04\", nil,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ArrayTypes\": {\n\t\t\ttbl:   \"ArrayTypes\",\n\t\t\twcols: arrayTypesKeys,\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeBoolValue(true),\n\t\t\t\t\tmakeBoolValue(false),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"eHl6\"),\n\t\t\t\t\tmakeStringValue(\"enp6\"),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"),\n\t\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.000000000Z\"),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"101\"),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeNumberValue(0.2),\n\t\t\t\t\tmakeNumberValue(0.5),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"2012-03-04\"),\n\t\t\t\t\tmakeStringValue(\"2012-03-05\"),\n\t\t\t\t)),\n\t\t\t},\n\t\t\tcols:  arrayTypesKeys,\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100),\n\t\t\t\t\tmakeTestArray(TCString, \"xxx\", \"yyy\"),\n\t\t\t\t\tmakeTestArray(TCBool, true, false),\n\t\t\t\t\tmakeTestArray(TCBytes, []byte(\"xyz\"), []byte(\"zzz\")),\n\t\t\t\t\tmakeTestArray(TCString, \"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.000000000Z\"),\n\t\t\t\t\tmakeTestArray(TCInt64, int64(100), int64(101)),\n\t\t\t\t\tmakeTestArray(TCFloat64, float64(0.2), float64(0.5)),\n\t\t\t\t\tmakeTestArray(TCString, \"2012-03-04\", \"2012-03-05\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Keyword\": {\n\t\t\ttbl:   \"From\",\n\t\t\twcols: fromTableKeys,\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"2\"),\n\t\t\t\tmakeStringValue(\"2\"),\n\t\t\t\tmakeStringValue(\"2\"),\n\t\t\t},\n\t\t\tcols:  fromTableKeys,\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(2), int64(2), int64(2)},\n\t\t\t},\n\t\t},\n\t\t\"GeneratedColumn\": {\n\t\t\ttbl:   \"GeneratedColumn\",\n\t\t\twcols: []string{\"Id\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t},\n\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"100\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, op := range []string{\"INSERT\", \"REPLACE\"} {\n\t\tt.Run(op, func(t *testing.T) {\n\t\t\tfor name, tt := range table {\n\t\t\t\tt.Run(name, func(t *testing.T) {\n\t\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\t\t\tdefer cancel()\n\n\t\t\t\t\tdb := newDatabase()\n\t\t\t\t\tses := newSession(db, \"foo\")\n\n\t\t\t\t\tfor _, s := range allSchema {\n\t\t\t\t\t\tddls := parseDDL(t, s)\n\t\t\t\t\t\tfor _, ddl := range ddls {\n\t\t\t\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\t\t\tlistValues := []*structpb.ListValue{\n\t\t\t\t\t\t\t{Values: tt.values},\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif op == \"INSERT\" {\n\t\t\t\t\t\t\tif err := db.Insert(ctx, tx, tt.tbl, tt.wcols, listValues); err != nil {\n\t\t\t\t\t\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if op == \"REPLACE\" {\n\t\t\t\t\t\t\tif err := db.Replace(ctx, tx, tt.tbl, tt.wcols, listValues); err != nil {\n\t\t\t\t\t\t\t\tt.Fatalf(\"Replace failed: %v\", err)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\n\t\t\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\t\t\tit, err := db.Read(ctx, tx, tt.tbl, \"\", tt.cols, &KeySet{All: true}, tt.limit)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Fatalf(\"Read failed: %v\", err)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvar rows [][]interface{}\n\t\t\t\t\t\terr = it.Do(func(row []interface{}) error {\n\t\t\t\t\t\t\trows = append(rows, row)\n\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t})\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif diff := cmp.Diff(tt.expected, rows); diff != \"\" {\n\t\t\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestInsertOrRepace_CommitTimestamp(t *testing.T) {\n\ttbl := \"FullTypes\"\n\twcols := []string{\n\t\t\"PKey\",\n\t\t\"FTString\",\n\t\t\"FTBool\",\n\t\t\"FTBytes\",\n\t\t\"FTTimestamp\",\n\t\t\"FTTimestampNull\",\n\t\t\"FTInt\",\n\t\t\"FTFloat\",\n\t\t\"FTDate\",\n\t}\n\tvalues := []*structpb.Value{\n\t\tmakeStringValue(\"xxx\"),                            // PKey STRING(32) NOT NULL,\n\t\tmakeStringValue(\"xxx\"),                            // FTString STRING(32) NOT NULL,\n\t\tmakeBoolValue(true),                               // FTBool BOOL NOT NULL,\n\t\tmakeStringValue(\"eHl6\"),                           // FTBytes BYTES(32) NOT NULL,\n\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestamp TIMESTAMP NOT NULL,\n\t\tmakeStringValue(\"spanner.commit_timestamp()\"),\n\t\tmakeStringValue(\"100\"),        // FTInt INT64 NOT NULL,\n\t\tmakeNumberValue(0.5),          // FTFloat FLOAT64 NOT NULL,\n\t\tmakeStringValue(\"2012-03-04\"), // FTDate DATE NOT NULL,\n\t}\n\tcols := []string{\"FTTimestamp\", \"FTTimestampNull\"}\n\tlimit := int64(100)\n\n\tfor _, op := range []string{\"INSERT\", \"REPLACE\"} {\n\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\tdefer cancel()\n\n\t\tdb := newDatabase()\n\t\tses := newSession(db, \"foo\")\n\n\t\tfor _, s := range allSchema {\n\t\t\tddls := parseDDL(t, s)\n\t\t\tfor _, ddl := range ddls {\n\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t}\n\t\t}\n\n\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\tlistValues := []*structpb.ListValue{\n\t\t\t\t{Values: values},\n\t\t\t}\n\t\t\tif op == \"INSERT\" {\n\t\t\t\tif err := db.Insert(ctx, tx, tbl, wcols, listValues); err != nil {\n\t\t\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t\t\t}\n\t\t\t} else if op == \"REPLACE\" {\n\t\t\t\tif err := db.Replace(ctx, tx, tbl, wcols, listValues); err != nil {\n\t\t\t\t\tt.Fatalf(\"Replace failed: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\tit, err := db.Read(ctx, tx, tbl, \"\", cols, &KeySet{All: true}, limit)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Read failed: %v\", err)\n\t\t\t}\n\n\t\t\tvar rows [][]interface{}\n\t\t\terr = it.Do(func(row []interface{}) error {\n\t\t\t\trows = append(rows, row)\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t\t\t}\n\n\t\t\tif len(rows) != 1 {\n\t\t\t\tt.Fatalf(\"unexpected numbers of rows: %v\", len(rows))\n\n\t\t\t}\n\t\t\trow := rows[0]\n\n\t\t\tvar a, b string\n\t\t\ta = row[0].(string)\n\t\t\tb = row[1].(string)\n\t\t\tif got := \"2012-03-04T12:34:56.123456789Z\"; a != got {\n\t\t\t\tt.Errorf(\"expect %v, but got %v\", got, a)\n\t\t\t}\n\n\t\t\ttimestamp, err := time.Parse(time.RFC3339Nano, b)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected format timestamp: %v\", err)\n\t\t\t}\n\n\t\t\td := time.Since(timestamp)\n\t\t\tif d >= 100*time.Millisecond {\n\t\t\t\tt.Fatalf(\"unexpected time: %v\", d)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestReplace(t *testing.T) {\n\ttable := map[string]struct {\n\t\ttbl    string\n\t\twcols  []string\n\t\tvalues []*structpb.Value\n\t\tcols   []string\n\t\tlimit  int64\n\n\t\texpected [][]interface{}\n\t}{\n\t\t\"Simple_NothingChanged\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t},\n\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"yyy\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_ConflictUpdate\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"zzz\"),\n\t\t\t},\n\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"zzz\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_Insert\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"101\"),\n\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t},\n\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t[]interface{}{int64(101), \"xxx\"},\n\t\t\t},\n\t\t},\n\t\t\"Composite_NothingChanged\": {\n\t\t\ttbl:   \"CompositePrimaryKeys\",\n\t\t\twcols: compositePrimaryKeysKeys,\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"1\"),        // Id INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"pkey1xxx\"), // PKey1 STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"100\"),      // PKey2 INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"2\"),        // Error INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"x1\"),       // X STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"y1\"),       // Y STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"z1\"),       // Z STRING(32) NOT NULL,\n\t\t\t},\n\t\t\tcols:  compositePrimaryKeysKeys,\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\n\t\t\t\t\tint64(1),\n\t\t\t\t\t\"pkey1xxx\",\n\t\t\t\t\tint64(100),\n\t\t\t\t\tint64(2),\n\t\t\t\t\t\"x1\",\n\t\t\t\t\t\"y1\",\n\t\t\t\t\t\"z1\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Composite_NothingChanged_RandomOrder\": {\n\t\t\ttbl:   \"CompositePrimaryKeys\",\n\t\t\twcols: []string{\"PKey2\", \"X\", \"PKey1\", \"Id\", \"Error\", \"Y\", \"Z\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),      // PKey2 INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"x1\"),       // X STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"pkey1xxx\"), // PKey1 STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"1\"),        // Id INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"2\"),        // Error INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"y1\"),       // Y STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"z1\"),       // Z STRING(32) NOT NULL,\n\t\t\t},\n\t\t\tcols:  compositePrimaryKeysKeys,\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\n\t\t\t\t\tint64(1),\n\t\t\t\t\t\"pkey1xxx\",\n\t\t\t\t\tint64(100),\n\t\t\t\t\tint64(2),\n\t\t\t\t\t\"x1\",\n\t\t\t\t\t\"y1\",\n\t\t\t\t\t\"z1\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Composite_ConflictUpdate_RandomOrder\": {\n\t\t\ttbl:   \"CompositePrimaryKeys\",\n\t\t\twcols: []string{\"PKey2\", \"X\", \"PKey1\", \"Id\", \"Error\", \"Y\", \"Z\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),      // PKey2 INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"x4\"),       // X STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"pkey1xxx\"), // PKey1 STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"1\"),        // Id INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"10000\"),    // Error INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"y4\"),       // Y STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"z4\"),       // Z STRING(32) NOT NULL,\n\t\t\t},\n\t\t\tcols:  compositePrimaryKeysKeys,\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\n\t\t\t\t\tint64(1),\n\t\t\t\t\t\"pkey1xxx\",\n\t\t\t\t\tint64(100),\n\t\t\t\t\tint64(10000),\n\t\t\t\t\t\"x4\",\n\t\t\t\t\t\"y4\",\n\t\t\t\t\t\"z4\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Keyword\": {\n\t\t\ttbl:   \"From\",\n\t\t\twcols: fromTableKeys,\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"1\"),\n\t\t\t\tmakeStringValue(\"2\"),\n\t\t\t\tmakeStringValue(\"3\"),\n\t\t\t},\n\t\t\tcols:  fromTableKeys,\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(1), int64(2), int64(3)},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tt := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\n\t\t\tdb := newDatabase()\n\t\t\tses := newSession(db, \"foo\")\n\n\t\t\tfor _, s := range allSchema {\n\t\t\t\tddls := parseDDL(t, s)\n\t\t\t\tfor _, ddl := range ddls {\n\t\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tcreateInitialData(t, ctx, db, tx)\n\t\t\t})\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tlistValues := []*structpb.ListValue{\n\t\t\t\t\t{Values: tt.values},\n\t\t\t\t}\n\t\t\t\tif err := db.Replace(ctx, tx, tt.tbl, tt.wcols, listValues); err != nil {\n\t\t\t\t\tt.Fatalf(\"Replace failed: %v\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tit, err := db.Read(ctx, tx, tt.tbl, \"\", tt.cols, &KeySet{All: true}, tt.limit)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Read failed: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tvar rows [][]interface{}\n\t\t\t\terr = it.Do(func(row []interface{}) error {\n\t\t\t\t\trows = append(rows, row)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tif diff := cmp.Diff(tt.expected, rows); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestUpdate(t *testing.T) {\n\ttable := map[string]struct {\n\t\ttbl    string\n\t\twcols  []string\n\t\tvalues []*structpb.Value\n\t\tcols   []string\n\t\tlimit  int64\n\n\t\texpected [][]interface{}\n\t}{\n\t\t\"Simple\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t},\n\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"yyy\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_UpdateWithSameValues\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t},\n\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_PKeyOnly\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t},\n\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t},\n\t\t},\n\t\t\"Composite_InOrder\": {\n\t\t\ttbl:   \"CompositePrimaryKeys\",\n\t\t\twcols: []string{\"PKey1\", \"PKey2\", \"X\", \"Y\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"pkey1xxx\"),\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"xxxxxxxxx\"),\n\t\t\t\tmakeStringValue(\"yyyyyyyyy\"),\n\t\t\t},\n\t\t\tcols:  compositePrimaryKeysKeys,\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\n\t\t\t\t\tint64(1),\n\t\t\t\t\t\"pkey1xxx\",\n\t\t\t\t\tint64(100),\n\t\t\t\t\tint64(2),\n\t\t\t\t\t\"xxxxxxxxx\",\n\t\t\t\t\t\"yyyyyyyyy\",\n\t\t\t\t\t\"z1\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Composite_PKeyReverse\": {\n\t\t\ttbl:   \"CompositePrimaryKeys\",\n\t\t\twcols: []string{\"PKey2\", \"PKey1\", \"X\", \"Y\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"pkey1xxx\"),\n\t\t\t\tmakeStringValue(\"xxxxxxxxx\"),\n\t\t\t\tmakeStringValue(\"yyyyyyyyy\"),\n\t\t\t},\n\t\t\tcols:  compositePrimaryKeysKeys,\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\n\t\t\t\t\tint64(1),\n\t\t\t\t\t\"pkey1xxx\",\n\t\t\t\t\tint64(100),\n\t\t\t\t\tint64(2),\n\t\t\t\t\t\"xxxxxxxxx\",\n\t\t\t\t\t\"yyyyyyyyy\",\n\t\t\t\t\t\"z1\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Composite_PKeyRandom\": {\n\t\t\ttbl:   \"CompositePrimaryKeys\",\n\t\t\twcols: []string{\"PKey2\", \"X\", \"PKey1\", \"Y\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"xxxxxxxxx\"),\n\t\t\t\tmakeStringValue(\"pkey1xxx\"),\n\t\t\t\tmakeStringValue(\"yyyyyyyyy\"),\n\t\t\t},\n\t\t\tcols:  compositePrimaryKeysKeys,\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\n\t\t\t\t\tint64(1),\n\t\t\t\t\t\"pkey1xxx\",\n\t\t\t\t\tint64(100),\n\t\t\t\t\tint64(2),\n\t\t\t\t\t\"xxxxxxxxx\",\n\t\t\t\t\t\"yyyyyyyyy\",\n\t\t\t\t\t\"z1\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"FullType_String\": {\n\t\t\ttbl:   \"FullTypes\",\n\t\t\twcols: []string{\"PKey\", \"FTString\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\tmakeStringValue(\"pppp\"),\n\t\t\t},\n\t\t\tcols:  fullTypesKeys,\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\n\t\t\t\t\t\"xxx\",\n\t\t\t\t\t\"pppp\", \"xxx\",\n\t\t\t\t\ttrue, true,\n\t\t\t\t\t[]byte(\"xyz\"), []byte(\"xyz\"),\n\t\t\t\t\t\"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.123456789Z\",\n\t\t\t\t\tint64(100), int64(100),\n\t\t\t\t\tfloat64(0.5), float64(0.5),\n\t\t\t\t\t\"2012-03-04\", \"2012-03-04\",\n\t\t\t\t},\n\t\t\t\t[]interface{}{\n\t\t\t\t\t\"yyy\",\n\t\t\t\t\t\"yyy\", \"yyy\",\n\t\t\t\t\ttrue, true,\n\t\t\t\t\t[]byte(\"xyz\"), []byte(\"xyz\"),\n\t\t\t\t\t\"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.123456789Z\",\n\t\t\t\t\tint64(101), int64(101),\n\t\t\t\t\tfloat64(0.5), float64(0.5),\n\t\t\t\t\t\"2012-03-04\", \"2012-03-04\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Keyword\": {\n\t\t\ttbl:   \"From\",\n\t\t\twcols: fromTableKeys,\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"1\"),\n\t\t\t\tmakeStringValue(\"2\"),\n\t\t\t\tmakeStringValue(\"3\"),\n\t\t\t},\n\t\t\tcols:  fromTableKeys,\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(1), int64(2), int64(3)},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tt := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\n\t\t\tdb := newDatabase()\n\t\t\tses := newSession(db, \"foo\")\n\n\t\t\tfor _, s := range allSchema {\n\t\t\t\tddls := parseDDL(t, s)\n\t\t\t\tfor _, ddl := range ddls {\n\t\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tcreateInitialData(t, ctx, db, tx)\n\t\t\t})\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tlistValues := []*structpb.ListValue{\n\t\t\t\t\t{Values: tt.values},\n\t\t\t\t}\n\t\t\t\tif err := db.Update(ctx, tx, tt.tbl, tt.wcols, listValues); err != nil {\n\t\t\t\t\tt.Fatalf(\"Update failed: %v\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tit, err := db.Read(ctx, tx, tt.tbl, \"\", tt.cols, &KeySet{All: true}, tt.limit)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Read failed: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tvar rows [][]interface{}\n\t\t\t\terr = it.Do(func(row []interface{}) error {\n\t\t\t\t\trows = append(rows, row)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tif diff := cmp.Diff(tt.expected, rows); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestInsertOrUpdate(t *testing.T) {\n\ttable := map[string]struct {\n\t\ttbl    string\n\t\twcols  []string\n\t\tvalues []*structpb.Value\n\t\tcols   []string\n\t\tlimit  int64\n\n\t\texpected [][]interface{}\n\t}{\n\t\t\"Simple_NothingChanged\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t},\n\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"yyy\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_ConflictUpdate\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"zzz\"),\n\t\t\t},\n\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"zzz\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_Insert\": {\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"101\"),\n\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t},\n\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t[]interface{}{int64(101), \"xxx\"},\n\t\t\t},\n\t\t},\n\t\t\"Composite_NothingChanged\": {\n\t\t\ttbl:   \"CompositePrimaryKeys\",\n\t\t\twcols: compositePrimaryKeysKeys,\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"1\"),        // Id INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"pkey1xxx\"), // PKey1 STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"100\"),      // PKey2 INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"2\"),        // Error INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"x1\"),       // X STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"y1\"),       // Y STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"z1\"),       // Z STRING(32) NOT NULL,\n\t\t\t},\n\t\t\tcols:  compositePrimaryKeysKeys,\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\n\t\t\t\t\tint64(1),\n\t\t\t\t\t\"pkey1xxx\",\n\t\t\t\t\tint64(100),\n\t\t\t\t\tint64(2),\n\t\t\t\t\t\"x1\",\n\t\t\t\t\t\"y1\",\n\t\t\t\t\t\"z1\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Composite_NothingChanged_RandomOrder\": {\n\t\t\ttbl:   \"CompositePrimaryKeys\",\n\t\t\twcols: []string{\"PKey2\", \"X\", \"PKey1\", \"Id\", \"Error\", \"Y\", \"Z\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),      // PKey2 INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"x1\"),       // X STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"pkey1xxx\"), // PKey1 STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"1\"),        // Id INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"2\"),        // Error INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"y1\"),       // Y STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"z1\"),       // Z STRING(32) NOT NULL,\n\t\t\t},\n\t\t\tcols:  compositePrimaryKeysKeys,\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\n\t\t\t\t\tint64(1),\n\t\t\t\t\t\"pkey1xxx\",\n\t\t\t\t\tint64(100),\n\t\t\t\t\tint64(2),\n\t\t\t\t\t\"x1\",\n\t\t\t\t\t\"y1\",\n\t\t\t\t\t\"z1\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Composite_ConflictUpdate_RandomOrder\": {\n\t\t\ttbl:   \"CompositePrimaryKeys\",\n\t\t\twcols: []string{\"PKey2\", \"X\", \"PKey1\", \"Id\", \"Error\", \"Y\", \"Z\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),      // PKey2 INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"x4\"),       // X STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"pkey1xxx\"), // PKey1 STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"1\"),        // Id INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"10000\"),    // Error INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"y4\"),       // Y STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"z4\"),       // Z STRING(32) NOT NULL,\n\t\t\t},\n\t\t\tcols:  compositePrimaryKeysKeys,\n\t\t\tlimit: 100,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\n\t\t\t\t\tint64(1),\n\t\t\t\t\t\"pkey1xxx\",\n\t\t\t\t\tint64(100),\n\t\t\t\t\tint64(10000),\n\t\t\t\t\t\"x4\",\n\t\t\t\t\t\"y4\",\n\t\t\t\t\t\"z4\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tt := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\n\t\t\tdb := newDatabase()\n\t\t\tses := newSession(db, name)\n\n\t\t\tfor _, s := range allSchema {\n\t\t\t\tddls := parseDDL(t, s)\n\t\t\t\tfor _, ddl := range ddls {\n\t\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tcreateInitialData(t, ctx, db, tx)\n\t\t\t})\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tlistValues := []*structpb.ListValue{\n\t\t\t\t\t{Values: tt.values},\n\t\t\t\t}\n\t\t\t\tif err := db.InsertOrUpdate(ctx, tx, tt.tbl, tt.wcols, listValues); err != nil {\n\t\t\t\t\tt.Fatalf(\"InsertOrUpdate failed: %v\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tit, err := db.Read(ctx, tx, tt.tbl, \"\", tt.cols, &KeySet{All: true}, tt.limit)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Read failed: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tvar rows [][]interface{}\n\t\t\t\terr = it.Do(func(row []interface{}) error {\n\t\t\t\t\trows = append(rows, row)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tif diff := cmp.Diff(tt.expected, rows); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestDelete(t *testing.T) {\n\ttable := map[string]struct {\n\t\ttbl  string\n\t\tks   *KeySet\n\t\tcols []string\n\n\t\texpected [][]interface{}\n\t}{\n\t\t\"Simple_All\": {\n\t\t\ttbl:      \"Simple\",\n\t\t\tks:       &KeySet{All: true},\n\t\t\tcols:     []string{\"Id\"},\n\t\t\texpected: nil,\n\t\t},\n\t\t\"Simple_Keys_Single\": {\n\t\t\ttbl: \"Simple\",\n\t\t\tks: &KeySet{\n\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\tmakeListValue(makeStringValue(\"100\")),\n\t\t\t\t},\n\t\t\t},\n\t\t\tcols: []string{\"Id\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t[]interface{}{int64(300)},\n\t\t\t},\n\t\t},\n\t\t\"Simple_Keys_MultiKeys\": {\n\t\t\ttbl: \"Simple\",\n\t\t\tks: &KeySet{\n\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\tmakeListValue(makeStringValue(\"100\")),\n\t\t\t\t\tmakeListValue(makeStringValue(\"300\")),\n\t\t\t\t},\n\t\t\t},\n\t\t\tcols: []string{\"Id\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(200)},\n\t\t\t},\n\t\t},\n\t\t\"Simple_Keys_NotExist\": {\n\t\t\ttbl: \"Simple\",\n\t\t\tks: &KeySet{\n\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\tmakeListValue(makeStringValue(\"1000\")),\n\t\t\t\t},\n\t\t\t},\n\t\t\tcols: []string{\"Id\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100)},\n\t\t\t\t[]interface{}{int64(200)},\n\t\t\t\t[]interface{}{int64(300)},\n\t\t\t},\n\t\t},\n\t\t\"Simple_KeyRange\": {\n\t\t\ttbl: \"Simple\",\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"100\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"200\")),\n\t\t\t\t\t\tstartClosed: true,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tcols: []string{\"Id\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(300)},\n\t\t\t},\n\t\t},\n\t\t\"CompositePrimaryKeys_Keys\": {\n\t\t\ttbl: \"CompositePrimaryKeys\",\n\t\t\tks: &KeySet{\n\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\tmakeListValue(makeStringValue(\"bbb\"), makeStringValue(\"1\")),\n\t\t\t\t\tmakeListValue(makeStringValue(\"ccc\"), makeStringValue(\"4\")),\n\t\t\t\t},\n\t\t\t},\n\t\t\tcols: []string{\"PKey1\", \"PKey2\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"aaa\", int64(1)},\n\t\t\t\t[]interface{}{\"bbb\", int64(3)},\n\t\t\t\t[]interface{}{\"bbb\", int64(2)},\n\t\t\t\t[]interface{}{\"ccc\", int64(3)},\n\t\t\t},\n\t\t},\n\t\t\"CompositePrimaryKeys_KeyRange\": {\n\t\t\ttbl: \"CompositePrimaryKeys\",\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"bbb\"), makeStringValue(\"4\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"bbb\"), makeStringValue(\"1\")),\n\t\t\t\t\t\tstartClosed: true,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tcols: []string{\"PKey1\", \"PKey2\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"aaa\", int64(1)},\n\t\t\t\t[]interface{}{\"ccc\", int64(4)},\n\t\t\t\t[]interface{}{\"ccc\", int64(3)},\n\t\t\t},\n\t\t},\n\n\t\t// Escape keywords\n\t\t\"Keyword_All\": {\n\t\t\ttbl:      \"From\",\n\t\t\tks:       &KeySet{All: true},\n\t\t\tcols:     []string{\"ALL\"},\n\t\t\texpected: nil,\n\t\t},\n\t\t\"Keyword_Keys\": {\n\t\t\ttbl: \"From\",\n\t\t\tks: &KeySet{\n\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\tmakeListValue(makeStringValue(\"1\")),\n\t\t\t\t},\n\t\t\t},\n\t\t\tcols:     []string{\"ALL\"},\n\t\t\texpected: nil,\n\t\t},\n\t\t\"Keyword_Ranges\": {\n\t\t\ttbl: \"From\",\n\t\t\tks: &KeySet{\n\t\t\t\tRanges: []*KeyRange{\n\t\t\t\t\t{\n\t\t\t\t\t\tstart:       makeListValue(makeStringValue(\"1\")),\n\t\t\t\t\t\tend:         makeListValue(makeStringValue(\"1\")),\n\t\t\t\t\t\tstartClosed: true,\n\t\t\t\t\t\tendClosed:   true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tcols:     []string{\"ALL\"},\n\t\t\texpected: nil,\n\t\t},\n\t}\n\n\tfor name, tt := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\n\t\t\tdb := newDatabase()\n\t\t\tses := newSession(db, name)\n\n\t\t\tfor _, s := range allSchema {\n\t\t\t\tddls := parseDDL(t, s)\n\t\t\t\tfor _, ddl := range ddls {\n\t\t\t\t\tif err := db.ApplyDDL(ctx, ddl); err != nil {\n\t\t\t\t\t\tt.Fatal(err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor _, query := range []string{\n\t\t\t\t`INSERT INTO Simple VALUES(100, \"xxx\")`,\n\t\t\t\t`INSERT INTO Simple VALUES(200, \"yyy\")`,\n\t\t\t\t`INSERT INTO Simple VALUES(300, \"zzz\")`,\n\n\t\t\t\t`INSERT INTO CompositePrimaryKeys VALUES(1, \"aaa\", 1, 0, \"x1\", \"y1\", \"z\")`,\n\t\t\t\t`INSERT INTO CompositePrimaryKeys VALUES(2, \"bbb\", 2, 0, \"x1\", \"y2\", \"z\")`,\n\t\t\t\t`INSERT INTO CompositePrimaryKeys VALUES(3, \"bbb\", 3, 0, \"x1\", \"y3\", \"z\")`,\n\t\t\t\t`INSERT INTO CompositePrimaryKeys VALUES(4, \"ccc\", 3, 0, \"x2\", \"y4\", \"z\")`,\n\t\t\t\t`INSERT INTO CompositePrimaryKeys VALUES(5, \"ccc\", 4, 0, \"x2\", \"y5\", \"z\")`,\n\n\t\t\t\t\"INSERT INTO `From` VALUES(1, 1, 1)\",\n\t\t\t} {\n\t\t\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tif err := db.Delete(ctx, tx, tt.tbl, tt.ks); err != nil {\n\t\t\t\t\tt.Fatalf(\"Delete failed: %v\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tit, err := db.Read(ctx, tx, tt.tbl, \"\", tt.cols, &KeySet{All: true}, 100)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Read failed: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tvar rows [][]interface{}\n\t\t\t\terr = it.Do(func(row []interface{}) error {\n\t\t\t\t\trows = append(rows, row)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tif diff := cmp.Diff(tt.expected, rows); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestMutationError(t *testing.T) {\n\ttable := map[string]struct {\n\t\top      []string\n\t\ttbl     string\n\t\twcols   []string\n\t\tvalues  []*structpb.Value\n\t\tcode    codes.Code\n\t\tmsg     *regexp.Regexp\n\t\tdetails []interface{}\n\t}{\n\t\t\"TableNotfound\": {\n\t\t\top:     []string{\"UPDATE\", \"INSERT\", \"UPSERT\", \"REPLACE\"},\n\t\t\ttbl:    \"XXX\",\n\t\t\twcols:  []string{},\n\t\t\tvalues: []*structpb.Value{},\n\t\t\tcode:   codes.NotFound,\n\t\t\tmsg:    regexp.MustCompile(`Table not found`),\n\t\t\tdetails: []interface{}{\n\t\t\t\t&errdetails.ResourceInfo{\n\t\t\t\t\tResourceType: \"spanner.googleapis.com/Table\",\n\t\t\t\t\tResourceName: \"XXX\",\n\t\t\t\t\tDescription:  \"Table not found\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ColumnNotFound\": {\n\t\t\top:    []string{\"UPDATE\", \"INSERT\", \"UPSERT\", \"REPLACE\"},\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\", \"XXX\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t},\n\t\t\tcode: codes.NotFound,\n\t\t\tmsg:  regexp.MustCompile(`Column not found in table Simple: XXX`),\n\t\t\tdetails: []interface{}{\n\t\t\t\t&errdetails.ResourceInfo{\n\t\t\t\t\tResourceType: \"spanner.googleapis.com/Column\",\n\t\t\t\t\tResourceName: \"XXX\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"MultipleValues\": {\n\t\t\top:    []string{\"UPDATE\", \"INSERT\", \"UPSERT\", \"REPLACE\"},\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t},\n\t\t\tcode:    codes.InvalidArgument,\n\t\t\tmsg:     regexp.MustCompile(`Multiple values for column Value`),\n\t\t\tdetails: []interface{}{},\n\t\t},\n\t\t\"NoPrimaryKeys\": {\n\t\t\top:    []string{\"UPDATE\", \"INSERT\", \"UPSERT\", \"REPLACE\"},\n\t\t\ttbl:   \"CompositePrimaryKeys\",\n\t\t\twcols: []string{\"PKey1\", \"Id\", \"Error\", \"X\", \"Y\", \"Z\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"1\"),\n\t\t\t\tmakeStringValue(\"x\"),\n\t\t\t\tmakeStringValue(\"y\"),\n\t\t\t\tmakeStringValue(\"z\"),\n\t\t\t},\n\t\t\tcode:    codes.FailedPrecondition,\n\t\t\tmsg:     regexp.MustCompile(`PKey2 must not be NULL in table CompositePrimaryKeys`),\n\t\t\tdetails: []interface{}{},\n\t\t},\n\t\t\"ValuesMisMatch\": {\n\t\t\top:    []string{\"UPDATE\", \"INSERT\", \"UPSERT\", \"REPLACE\"},\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t},\n\t\t\tcode:    codes.InvalidArgument,\n\t\t\tmsg:     regexp.MustCompile(`Mutation has mismatched number of columns and values.`),\n\t\t\tdetails: []interface{}{},\n\t\t},\n\t\t\"RowNotFound\": {\n\t\t\top:    []string{\"UPDATE\"},\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100000\"),\n\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t},\n\t\t\tcode:    codes.NotFound,\n\t\t\tmsg:     regexp.MustCompile(`Row \\[.*\\] in table Simple is missing. Row cannot be updated.`),\n\t\t\tdetails: []interface{}{}, // no details\n\t\t},\n\t\t\"ValuesNotSpecifiedForPKey\": {\n\t\t\top:    []string{\"INSERT\", \"REPLACE\"},\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t},\n\t\t\tcode:    codes.FailedPrecondition,\n\t\t\tmsg:     regexp.MustCompile(`Id must not be NULL in table Simple.`),\n\t\t\tdetails: []interface{}{},\n\t\t},\n\t\t\"NullSpecifiedForPKey\": {\n\t\t\top:    []string{\"INSERT\", \"REPLACE\"},\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeNullValue(),\n\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t},\n\t\t\tcode:    codes.FailedPrecondition,\n\t\t\tmsg:     regexp.MustCompile(`Id must not be NULL in table Simple.`),\n\t\t\tdetails: []interface{}{},\n\t\t},\n\t\t\"ValuesNotSpecifiedForNonNullable\": {\n\t\t\top:    []string{\"INSERT\", \"REPLACE\"},\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t},\n\t\t\tcode:    codes.FailedPrecondition,\n\t\t\tmsg:     regexp.MustCompile(`A new row in table Simple does not specify a non-null value for these NOT NULL columns: Value`),\n\t\t\tdetails: []interface{}{},\n\t\t},\n\t\t\"NullSpecifiedForNonNullable\": {\n\t\t\top:    []string{\"INSERT\", \"REPLACE\"},\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeNullValue(),\n\t\t\t},\n\t\t\tcode:    codes.FailedPrecondition,\n\t\t\tmsg:     regexp.MustCompile(`Value must not be NULL in table Simple.`),\n\t\t\tdetails: []interface{}{},\n\t\t},\n\n\t\t\"PrimaryKeyViolation\": {\n\t\t\top:    []string{\"INSERT\"},\n\t\t\ttbl:   \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"yyyy\"),\n\t\t\t},\n\t\t\tcode:    codes.AlreadyExists,\n\t\t\tmsg:     regexp.MustCompile(`Row \\[.*\\] in table Simple already exists`),\n\t\t\tdetails: []interface{}{},\n\t\t},\n\n\t\t\"FullType\": {\n\t\t\top:    []string{\"INSERT\"},\n\t\t\ttbl:   \"FullTypes\",\n\t\t\twcols: fullTypesKeys,\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"zzz\"),                            // PKey STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"xxx\"),                            // FTString STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"xxx\"),                            // FTStringNull STRING(32),\n\t\t\t\tmakeBoolValue(true),                               // FTBool BOOL NOT NULL,\n\t\t\t\tmakeBoolValue(true),                               // FTBoolNull BOOL,\n\t\t\t\tmakeStringValue(\"eHl6\"),                           // FTBytes BYTES(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"eHl6\"),                           // FTBytesNull BYTES(32),\n\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestamp TIMESTAMP NOT NULL,\n\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestampNull TIMESTAMP,\n\t\t\t\tmakeStringValue(\"100\"),                            // FTInt INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"100\"),                            // FTIntNull INT64,\n\t\t\t\tmakeNumberValue(0.5),                              // FTFloat FLOAT64 NOT NULL,\n\t\t\t\tmakeNumberValue(0.5),                              // FTFloatNull FLOAT64,\n\t\t\t\tmakeStringValue(\"2012-03-04\"),                     // FTDate DATE NOT NULL,\n\t\t\t\tmakeStringValue(\"2012-03-04\"),                     // FTDateNull DATE,\n\t\t\t},\n\t\t\tcode:    codes.AlreadyExists,\n\t\t\tmsg:     regexp.MustCompile(`Unique index violation at index key \\[.*\\]. It conflicts with row \\[.*\\] in table FullTypes`),\n\t\t\tdetails: []interface{}{},\n\t\t},\n\t\t// A new record coflicts only secondary index but it does not replace\n\t\t// \"FullTypeConflictsSecondaryIndex\": {\n\t\t// \top:    []string{\"REPLACE\", \"UPSERT\"},\n\t\t// \ttbl:   \"FullTypes\",\n\t\t// \twcols: fullTypesKeys,\n\t\t// \tvalues: []*structpb.Value{\n\t\t// \t\tmakeStringValue(\"zzzzzzzz\"),                       // PKey STRING(32) NOT NULL,\n\t\t// \t\tmakeStringValue(\"xxx\"),                            // FTString STRING(32) NOT NULL,\n\t\t// \t\tmakeStringValue(\"xxx\"),                            // FTStringNull STRING(32),\n\t\t// \t\tmakeBoolValue(true),                               // FTBool BOOL NOT NULL,\n\t\t// \t\tmakeBoolValue(true),                               // FTBoolNull BOOL,\n\t\t// \t\tmakeStringValue(\"xyz\"),                            // FTBytes BYTES(32) NOT NULL,\n\t\t// \t\tmakeStringValue(\"xyz\"),                            // FTBytesNull BYTES(32),\n\t\t// \t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestamp TIMESTAMP NOT NULL,\n\t\t// \t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestampNull TIMESTAMP,\n\t\t// \t\tmakeStringValue(\"300\"),                            // FTInt INT64 NOT NULL,\n\t\t// \t\tmakeStringValue(\"300\"),                            // FTIntNull INT64,\n\t\t// \t\tmakeNumberValue(0.5),                              // FTFloat FLOAT64 NOT NULL,\n\t\t// \t\tmakeNumberValue(0.5),                              // FTFloatNull FLOAT64,\n\t\t// \t\tmakeStringValue(\"2012-03-04\"),                     // FTDate DATE NOT NULL,\n\t\t// \t\tmakeStringValue(\"2012-03-04\"),                     // FTDateNull DATE,\n\t\t// \t},\n\t\t// \tcode: codes.AlreadyExists,\n\t\t// \tmsg:  regexp.MustCompile(`Unique index violation at index key \\[.*\\]. It conflicts with row \\[.*\\] in table FullTypes`),\n\t\t// },\n\n\t\t\"FullType_CommitTimestamp\": {\n\t\t\top:    []string{\"INSERT\"},\n\t\t\ttbl:   \"FullTypes\",\n\t\t\twcols: fullTypesKeys,\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"zzz\"),                            // PKey STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"zzz\"),                            // FTString STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"zzz\"),                            // FTStringNull STRING(32),\n\t\t\t\tmakeBoolValue(true),                               // FTBool BOOL NOT NULL,\n\t\t\t\tmakeBoolValue(true),                               // FTBoolNull BOOL,\n\t\t\t\tmakeStringValue(\"eHl6\"),                           // FTBytes BYTES(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"eHl6\"),                           // FTBytesNull BYTES(32),\n\t\t\t\tmakeStringValue(\"spanner.commit_timestamp()\"),     // FTTimestamp TIMESTAMP NOT NULL,\n\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestampNull TIMESTAMP,\n\t\t\t\tmakeStringValue(\"999\"),                            // FTInt INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"100\"),                            // FTIntNull INT64,\n\t\t\t\tmakeNumberValue(0.5),                              // FTFloat FLOAT64 NOT NULL,\n\t\t\t\tmakeNumberValue(0.5),                              // FTFloatNull FLOAT64,\n\t\t\t\tmakeStringValue(\"2012-03-04\"),                     // FTDate DATE NOT NULL,\n\t\t\t\tmakeStringValue(\"2012-03-04\"),                     // FTDateNull DATE,\n\t\t\t},\n\t\t\tcode:    codes.InvalidArgument, // TODO:  FailedPrecondition\n\t\t\tmsg:     regexp.MustCompile(`Cannot write commit timestamp because the allow_commit_timestamp column option is not set to true for column`),\n\t\t\tdetails: []interface{}{},\n\t\t},\n\t\t\"FullType_ExceedsMaximumSize\": {\n\t\t\top:    []string{\"INSERT\"},\n\t\t\ttbl:   \"FullTypes\",\n\t\t\twcols: fullTypesKeys,\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(strings.Repeat(\"z\", 1000)),        // PKey STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"zzz\"),                            // FTString STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"zzz\"),                            // FTStringNull STRING(32),\n\t\t\t\tmakeBoolValue(true),                               // FTBool BOOL NOT NULL,\n\t\t\t\tmakeBoolValue(true),                               // FTBoolNull BOOL,\n\t\t\t\tmakeStringValue(\"eHl6\"),                           // FTBytes BYTES(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"eHl6\"),                           // FTBytesNull BYTES(32),\n\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestamp TIMESTAMP NOT NULL,\n\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestampNull TIMESTAMP,\n\t\t\t\tmakeStringValue(\"999\"),                            // FTInt INT64 NOT NULL,\n\t\t\t\tmakeStringValue(\"100\"),                            // FTIntNull INT64,\n\t\t\t\tmakeNumberValue(0.5),                              // FTFloat FLOAT64 NOT NULL,\n\t\t\t\tmakeNumberValue(0.5),                              // FTFloatNull FLOAT64,\n\t\t\t\tmakeStringValue(\"2012-03-04\"),                     // FTDate DATE NOT NULL,\n\t\t\t\tmakeStringValue(\"2012-03-04\"),                     // FTDateNull DATE,\n\t\t\t},\n\t\t\tcode: codes.FailedPrecondition,\n\t\t\t// msg:  regexp.MustCompile(`New value exceeds the maximum size limit for this column in this database: FullTypes.PKey, size: 1000, limit: 32.`),\n\t\t\tmsg:     regexp.MustCompile(\"CHECK constraint failed: LENGTH\\\\(`PKey`\\\\) <= 32\"),\n\t\t\tdetails: []interface{}{},\n\t\t},\n\t}\n\n\tfor name, tt := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\n\t\t\tdb := newDatabase()\n\t\t\tses := newSession(db, \"foo\")\n\n\t\t\tfor _, s := range allSchema {\n\t\t\t\tddls := parseDDL(t, s)\n\t\t\t\tfor _, ddl := range ddls {\n\t\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tcreateInitialData(t, ctx, db, tx)\n\t\t\t})\n\n\t\t\tfor _, op := range tt.op {\n\t\t\t\tt.Run(op, func(t *testing.T) {\n\t\t\t\t\tlistValues := []*structpb.ListValue{\n\t\t\t\t\t\t{Values: tt.values},\n\t\t\t\t\t}\n\n\t\t\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\t\t\tvar err error\n\t\t\t\t\t\tswitch op {\n\t\t\t\t\t\tcase \"INSERT\":\n\t\t\t\t\t\t\terr = db.Insert(ctx, tx, tt.tbl, tt.wcols, listValues)\n\t\t\t\t\t\tcase \"UPDATE\":\n\t\t\t\t\t\t\terr = db.Update(ctx, tx, tt.tbl, tt.wcols, listValues)\n\t\t\t\t\t\tcase \"UPSERT\":\n\t\t\t\t\t\t\terr = db.InsertOrUpdate(ctx, tx, tt.tbl, tt.wcols, listValues)\n\t\t\t\t\t\tcase \"REPLACE\":\n\t\t\t\t\t\t\terr = db.Replace(ctx, tx, tt.tbl, tt.wcols, listValues)\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tt.Fatalf(\"unexpected op: %v\", op)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tst := status.Convert(err)\n\t\t\t\t\t\tif st.Code() != tt.code {\n\t\t\t\t\t\t\tt.Errorf(\"expect code to be %v but got %v\", tt.code, st.Code())\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !tt.msg.MatchString(st.Message()) {\n\t\t\t\t\t\t\tt.Errorf(\"unexpected error message: %v\", st.Message())\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif diff := cmp.Diff(tt.details, st.Details(), protocmp.Transform()); diff != \"\" {\n\t\t\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestExecute(t *testing.T) {\n\ttable := map[string]struct {\n\t\tsql    string\n\t\tparams map[string]Value\n\n\t\tcode          codes.Code\n\t\tmsg           *regexp.Regexp\n\t\texpectedCount int64\n\t\ttable         string\n\t\tcols          []string\n\t\texpected      [][]interface{}\n\t}{\n\t\t\"Simple_Update\": {\n\t\t\tsql:           `UPDATE Simple SET Value = \"zzz\" WHERE Id = 100`,\n\t\t\texpectedCount: 1,\n\t\t\ttable:         \"Simple\",\n\t\t\tcols:          []string{\"Id\", \"Value\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"zzz\"},\n\t\t\t},\n\t\t},\n\t\t// TODO\n\t\t// \"Simple_Update_Alias\": {\n\t\t// \tsql:           `UPDATE Simple AS s SET s.Value = \"zzz\" WHERE s.Id = 100`,\n\t\t// \texpectedCount: 1,\n\t\t// \ttable:         \"Simple\",\n\t\t// \tcols:          []string{\"Id\", \"Value\"},\n\t\t// \texpected: [][]interface{}{\n\t\t// \t\t[]interface{}{int64(100), \"zzz\"},\n\t\t// \t},\n\t\t// },\n\t\t\"Simple_Update_ParamInWhere\": {\n\t\t\tsql: `UPDATE Simple SET Value = \"zzz\" WHERE Id = @id`,\n\t\t\tparams: map[string]Value{\n\t\t\t\t\"id\": makeTestValue(100),\n\t\t\t},\n\t\t\texpectedCount: 1,\n\t\t\ttable:         \"Simple\",\n\t\t\tcols:          []string{\"Id\", \"Value\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"zzz\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_Update_ParamInValue\": {\n\t\t\tsql: `UPDATE Simple SET Value = @value WHERE Id = 100`,\n\t\t\tparams: map[string]Value{\n\t\t\t\t\"value\": makeTestValue(\"zzz\"),\n\t\t\t},\n\t\t\texpectedCount: 1,\n\t\t\ttable:         \"Simple\",\n\t\t\tcols:          []string{\"Id\", \"Value\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"zzz\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_Update_MultipleItems\": {\n\t\t\tsql:  `UPDATE Simple SET Value = \"zzz\", Value = \"zzz\" WHERE Id = 100`,\n\t\t\tcode: codes.InvalidArgument,\n\t\t\tmsg:  regexp.MustCompile(`Update item Value assigned more than once`),\n\t\t},\n\t\t\"Simple_Update_MultipleItems2\": {\n\t\t\tsql:  `UPDATE Simple AS s SET s.Value = \"zzz\", Value = \"zzz\" WHERE Id = 100`,\n\t\t\tcode: codes.InvalidArgument,\n\t\t\tmsg:  regexp.MustCompile(`Update item Value assigned more than once`),\n\t\t},\n\t\t\"Simple_Update_MultipleItems3\": {\n\t\t\tsql:  `UPDATE Simple AS s SET Value = \"zzz\", s.Value = \"zzz\" WHERE Id = 100`,\n\t\t\tcode: codes.InvalidArgument,\n\t\t\tmsg:  regexp.MustCompile(`Update item s.Value assigned more than once`),\n\t\t},\n\t\t\"Simple_Update_MultipleItems4\": {\n\t\t\tsql:  `UPDATE Simple AS s SET s.Value = \"zzz\", s.Value = \"zzz\" WHERE Id = 100`,\n\t\t\tcode: codes.InvalidArgument,\n\t\t\tmsg:  regexp.MustCompile(`Update item s.Value assigned more than once`),\n\t\t},\n\t\t\"Simple_Update_MultipleItems5\": {\n\t\t\tsql:  `UPDATE Simple SET Simple.Value = \"zzz\", Value = \"zzz\" WHERE Id = 100`,\n\t\t\tcode: codes.InvalidArgument,\n\t\t\tmsg:  regexp.MustCompile(`Update item Value assigned more than once`),\n\t\t},\n\t\t\"Simple_Update_ColumnNotFound\": {\n\t\t\tsql:  `UPDATE Simple SET Valu = \"zzz\" WHERE Id = 100`,\n\t\t\tcode: codes.InvalidArgument,\n\t\t\tmsg:  regexp.MustCompile(`Unrecognized name: Valu`),\n\t\t},\n\t\t\"Simple_Update_PathNotFound\": {\n\t\t\tsql:  `UPDATE Simple AS s SET s.Valu = \"zzz\" WHERE Id = 100`,\n\t\t\tcode: codes.InvalidArgument,\n\t\t\tmsg:  regexp.MustCompile(`Name Valu not found inside s`),\n\t\t},\n\t\t\"EscapeKeyword_Update\": {\n\t\t\tsql:           \"UPDATE `From` SET `CAST` = 2 WHERE `ALL` = 1\",\n\t\t\texpectedCount: 1,\n\t\t\ttable:         \"From\",\n\t\t\tcols:          fromTableKeys,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(1), int64(2), int64(1)},\n\t\t\t},\n\t\t},\n\t\t// TODO\n\t\t// \"EscapeKeyword_Update_Alias\": {\n\t\t// \tsql:           \"UPDATE `From` AS `AND` SET `AND`.`CAST` = 2 WHERE `ALL` = 1\",\n\t\t// \texpectedCount: 1,\n\t\t// \ttable:         \"From\",\n\t\t// \tcols:          fromTableKeys,\n\t\t// \texpected: [][]interface{}{\n\t\t// \t\t[]interface{}{int64(1), int64(2), int64(1)},\n\t\t// \t},\n\t\t// },\n\t\t\"Simple_Insert\": {\n\t\t\tsql:           `INSERT INTO Simple (Id, Value) VALUES(101, \"yyy\")`,\n\t\t\texpectedCount: 1,\n\t\t\ttable:         \"Simple\",\n\t\t\tcols:          []string{\"Id\", \"Value\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t[]interface{}{int64(101), \"yyy\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_Insert_Param\": {\n\t\t\tsql: `INSERT INTO Simple (Id, Value) VALUES(@a, @b)`,\n\t\t\tparams: map[string]Value{\n\t\t\t\t\"a\": makeTestValue(\"1000\"),\n\t\t\t\t\"b\": makeTestValue(\"bbbb\"),\n\t\t\t},\n\t\t\texpectedCount: 1,\n\t\t\ttable:         \"Simple\",\n\t\t\tcols:          []string{\"Id\", \"Value\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t[]interface{}{int64(1000), \"bbbb\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_InsertMulti\": {\n\t\t\tsql:           `INSERT INTO Simple (Id, Value) VALUES(101, \"yyy\"), (102, \"zzz\")`,\n\t\t\texpectedCount: 2,\n\t\t\ttable:         \"Simple\",\n\t\t\tcols:          []string{\"Id\", \"Value\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t[]interface{}{int64(101), \"yyy\"},\n\t\t\t\t[]interface{}{int64(102), \"zzz\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_Insert_Query\": {\n\t\t\tsql:           `INSERT INTO Simple (Id, Value) SELECT FTInt+1, PKey FROM FullTypes`,\n\t\t\texpectedCount: 2,\n\t\t\ttable:         \"Simple\",\n\t\t\tcols:          []string{\"Id\", \"Value\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t[]interface{}{int64(101), \"xxx\"},\n\t\t\t\t[]interface{}{int64(102), \"yyy\"},\n\t\t\t},\n\t\t},\n\t\t\"Simple_Insert_Subquery\": {\n\t\t\tsql:           `INSERT INTO Simple (Id, Value) VALUES(200, (SELECT PKey FROM FullTypes WHERE PKey = \"xxx\"))`,\n\t\t\texpectedCount: 1,\n\t\t\ttable:         \"Simple\",\n\t\t\tcols:          []string{\"Id\", \"Value\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t[]interface{}{int64(200), \"xxx\"},\n\t\t\t},\n\t\t},\n\t\t// \"Simple_Insert_Unnest\": {\n\t\t// \tsql: `INSERT INTO Simple (Id, Value) SELECT * FROM UNNEST ([(200, \"y\"), (300, \"z\")])`,\n\n\t\t// \texpectedCount: 2,\n\t\t// \ttable:         \"Simple\",\n\t\t// \tcols:          []string{\"Id\", \"Value\"},\n\t\t// \texpected: [][]interface{}{\n\t\t// \t\t[]interface{}{int64(100), \"xxx\"},\n\t\t// \t\t[]interface{}{int64(200), \"y\"},\n\t\t// \t\t[]interface{}{int64(300), \"z\"},\n\t\t// \t},\n\t\t// },\n\t\t\"Simple_Insert_NonNullValue\": {\n\t\t\tsql:  `INSERT INTO Simple (Id) VALUES(101)`,\n\t\t\tcode: codes.FailedPrecondition,\n\t\t\tmsg:  regexp.MustCompile(`A new row in table Simple does not specify a non-null value for these NOT NULL columns: Value`),\n\t\t},\n\t\t\"Simple_Insert_MultipleKeys\": {\n\t\t\tsql:  `INSERT INTO Simple (Id, Id, Value) VALUES(101, 102, \"x\")`,\n\t\t\tcode: codes.InvalidArgument,\n\t\t\tmsg:  regexp.MustCompile(`INSERT has columns with duplicate name: Id`),\n\t\t},\n\t\t\"Simple_Insert_WrongColumnCount\": {\n\t\t\tsql:  `INSERT INTO Simple (Id, Value) VALUES(101, 102, \"x\")`,\n\t\t\tcode: codes.InvalidArgument,\n\t\t\tmsg:  regexp.MustCompile(`Inserted row has wrong column count; Has 3, expected 2`),\n\t\t},\n\t\t\"Simple_Insert_ColumnNotFound\": {\n\t\t\tsql:  `INSERT INTO Simple (Id, Valueee) VALUES(101, \"x\")`,\n\t\t\tcode: codes.InvalidArgument,\n\t\t\tmsg:  regexp.MustCompile(`Column Valueee is not present in table Simple`),\n\t\t},\n\t\t\"EscapeKeyword_Insert\": {\n\t\t\tsql:           \"INSERT INTO `From` (`ALL`, `CAST`, `JOIN`) VALUES(2, 2, 2)\",\n\t\t\texpectedCount: 1,\n\t\t\ttable:         \"From\",\n\t\t\tcols:          fromTableKeys,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(1), int64(1), int64(1)},\n\t\t\t\t[]interface{}{int64(2), int64(2), int64(2)},\n\t\t\t},\n\t\t},\n\t\t\"Simple_Delete\": {\n\t\t\tsql:           `DELETE FROM Simple WHERE Id = 100`,\n\t\t\texpectedCount: 1,\n\t\t\ttable:         \"Simple\",\n\t\t\tcols:          []string{\"Id\", \"Value\"},\n\t\t\texpected:      nil,\n\t\t},\n\t\t\"Simple_Delete_SubQuery\": {\n\t\t\tsql:           `DELETE FROM Simple WHERE Id IN (SELECT Id FROM Simple)`,\n\t\t\texpectedCount: 1,\n\t\t\ttable:         \"Simple\",\n\t\t\tcols:          []string{\"Id\", \"Value\"},\n\t\t\texpected:      nil,\n\t\t},\n\t\t\"Simple_Delete_Param\": {\n\t\t\tsql: `DELETE FROM Simple WHERE Id = @id`,\n\t\t\tparams: map[string]Value{\n\t\t\t\t\"id\": makeTestValue(100),\n\t\t\t},\n\t\t\texpectedCount: 1,\n\t\t\ttable:         \"Simple\",\n\t\t\tcols:          []string{\"Id\", \"Value\"},\n\t\t\texpected:      nil,\n\t\t},\n\t\t\"Simple_Delete_NotExist\": {\n\t\t\tsql:           `DELETE FROM Simple WHERE Id = 10000`,\n\t\t\texpectedCount: 0,\n\t\t\ttable:         \"Simple\",\n\t\t\tcols:          []string{\"Id\", \"Value\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t},\n\t\t},\n\t\t\"EscapeKeyword_Delete\": {\n\t\t\tsql:           \"DELETE FROM `From` WHERE `ALL` = 1\",\n\t\t\texpectedCount: 1,\n\t\t\ttable:         \"From\",\n\t\t\tcols:          fromTableKeys,\n\t\t\texpected:      nil,\n\t\t},\n\t}\n\n\tfor name, tt := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\n\t\t\tstmt, err := (&memefish.Parser{\n\t\t\t\tLexer: &memefish.Lexer{\n\t\t\t\t\tFile: &token.File{FilePath: \"\", Buffer: tt.sql},\n\t\t\t\t},\n\t\t\t}).ParseDML()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to parse DML: %q %v\", tt.sql, err)\n\t\t\t}\n\n\t\t\tdb := newDatabase()\n\t\t\tses := newSession(db, \"foo\")\n\n\t\t\tfor _, s := range allSchema {\n\t\t\t\tddls := parseDDL(t, s)\n\t\t\t\tfor _, ddl := range ddls {\n\t\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tcreateInitialData(t, ctx, db, tx)\n\t\t\t})\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tr, err := db.Execute(ctx, tx, stmt, tt.params)\n\t\t\t\tif tt.code == codes.OK {\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"Execute failed: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif r != tt.expectedCount {\n\t\t\t\t\t\tt.Fatalf(\"expect count to be %v, but got %v\", tt.expectedCount, r)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tst := status.Convert(err)\n\t\t\t\t\tif st.Code() != tt.code {\n\t\t\t\t\t\tt.Errorf(\"expect code to be %v but got %v\", tt.code, st.Code())\n\t\t\t\t\t}\n\t\t\t\t\tif !tt.msg.MatchString(st.Message()) {\n\t\t\t\t\t\tt.Errorf(\"unexpected error message: \\n %q\\n expected:\\n %q\", st.Message(), tt.msg)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tif tt.code != codes.OK {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// check database values\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tit, err := db.Read(ctx, tx, tt.table, \"\", tt.cols, &KeySet{All: true}, 100)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Read failed: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tvar rows [][]interface{}\n\t\t\t\terr = it.Do(func(row []interface{}) error {\n\t\t\t\t\trows = append(rows, row)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tif diff := cmp.Diff(tt.expected, rows); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestInterleaveInsert(t *testing.T) {\n\ttype tableConfig struct {\n\t\ttbl      string\n\t\twcols    []string\n\t\tvalues   []*structpb.Value\n\t\tcols     []string\n\t\tlimit    int64\n\t\texpected [][]interface{}\n\t}\n\n\ttable := map[string]struct {\n\t\tchild        *tableConfig\n\t\tparent       *tableConfig\n\t\texpectsError bool\n\t}{\n\t\t\"InsertWithoutParent\": {\n\t\t\tchild: &tableConfig{\n\t\t\t\ttbl:   \"Interleaved\",\n\t\t\t\twcols: []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols:     []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tlimit:    100,\n\t\t\t\texpected: nil,\n\t\t\t},\n\t\t\texpectsError: true,\n\t\t},\n\t\t\"InsertWithParent\": {\n\t\t\tparent: &tableConfig{\n\t\t\t\ttbl:   \"ParentTable\",\n\t\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\t\tlimit: 100,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tchild: &tableConfig{\n\t\t\t\ttbl:   \"Interleaved\",\n\t\t\t\twcols: []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols:  []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tlimit: 100,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectsError: false,\n\t\t},\n\t\t\"InsertWithoutParent(Cascade)\": {\n\t\t\tchild: &tableConfig{\n\t\t\t\ttbl:   \"Interleaved\",\n\t\t\t\twcols: []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols:     []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tlimit:    100,\n\t\t\t\texpected: nil,\n\t\t\t},\n\t\t\texpectsError: true,\n\t\t},\n\t\t\"InsertWithParent(Cascade)\": {\n\t\t\tparent: &tableConfig{\n\t\t\t\ttbl:   \"ParentTable\",\n\t\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\t\tlimit: 100,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tchild: &tableConfig{\n\t\t\t\ttbl:   \"Interleaved\",\n\t\t\t\twcols: []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols:  []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tlimit: 100,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectsError: false,\n\t\t},\n\t\t\"InsertWithoutParent(NoAction)\": {\n\t\t\tchild: &tableConfig{\n\t\t\t\ttbl:   \"Interleaved\",\n\t\t\t\twcols: []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols:     []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tlimit:    100,\n\t\t\t\texpected: nil,\n\t\t\t},\n\t\t\texpectsError: true,\n\t\t},\n\t\t\"InsertWithParent(NoAction)\": {\n\t\t\tparent: &tableConfig{\n\t\t\t\ttbl:   \"ParentTable\",\n\t\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\t\tlimit: 100,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tchild: &tableConfig{\n\t\t\t\ttbl:   \"Interleaved\",\n\t\t\t\twcols: []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols:  []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tlimit: 100,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t[]interface{}{int64(100), int64(100), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectsError: false,\n\t\t},\n\t}\n\n\tfor name, tt := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx := context.Background()\n\t\t\tdb := newDatabase()\n\t\t\tses := newSession(db, \"foo\")\n\t\t\tfor _, s := range allSchema {\n\t\t\t\tddls := parseDDL(t, s)\n\t\t\t\tfor _, ddl := range ddls {\n\t\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif tt.parent != nil {\n\t\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\t\ttestInsertHelper(t, ctx, db, tx, tt.parent.tbl, tt.parent.cols, tt.parent.wcols, tt.parent.values, tt.parent.limit, tt.parent.expected)\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tif tt.expectsError {\n\t\t\t\tlistValues := []*structpb.ListValue{\n\t\t\t\t\t{Values: tt.child.values},\n\t\t\t\t}\n\t\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\t\tif err := db.Insert(ctx, tx, tt.child.tbl, tt.child.wcols, listValues); err == nil {\n\t\t\t\t\t\tt.Fatalf(\"Insert succeeded even though it should fail: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\ttestInsertHelper(t, ctx, db, tx, tt.child.tbl, tt.child.cols, tt.child.wcols, tt.child.values, tt.child.limit, tt.child.expected)\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc testInsertHelper(\n\tt *testing.T,\n\tctx context.Context,\n\tdb *database,\n\ttx *transaction,\n\ttbl string,\n\tcols []string,\n\twcols []string,\n\tvalues []*structpb.Value,\n\tlimit int64,\n\texpected [][]interface{},\n) {\n\tt.Helper()\n\n\tlistValues := []*structpb.ListValue{\n\t\t{Values: values},\n\t}\n\n\tif err := db.Insert(ctx, tx, tbl, wcols, listValues); err != nil {\n\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t}\n\n\tit, err := db.Read(ctx, tx, tbl, \"\", cols, &KeySet{All: true}, limit)\n\tif err != nil {\n\t\tt.Fatalf(\"Read failed: %v\", err)\n\t}\n\n\tvar rows [][]interface{}\n\terr = it.Do(func(row []interface{}) error {\n\t\trows = append(rows, row)\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t}\n\n\tif diff := cmp.Diff(expected, rows); diff != \"\" {\n\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t}\n}\n\nfunc TestInterleaveDeleteParent(t *testing.T) {\n\ttype tableConfig struct {\n\t\ttbl    string\n\t\twcols  []string\n\t\tvalues []*structpb.Value\n\t\tcols   []string\n\t\tks     *KeySet\n\t}\n\n\ttable := map[string]struct {\n\t\tchild              *tableConfig\n\t\tparent             *tableConfig\n\t\texpectsDeleteError bool\n\t}{\n\t\t\"DeleteDefault\": {\n\t\t\tparent: &tableConfig{\n\t\t\t\ttbl:   \"ParentTable\",\n\t\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols: []string{\"Id\", \"Value\"},\n\t\t\t\tks: &KeySet{\n\t\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\t\tmakeListValue(makeStringValue(\"100\")),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tchild: &tableConfig{\n\t\t\t\ttbl:   \"Interleaved\",\n\t\t\t\twcols: []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols: []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tks: &KeySet{\n\t\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\t\tmakeListValue(\n\t\t\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectsDeleteError: true,\n\t\t},\n\t\t\"DeleteCascade\": {\n\t\t\tparent: &tableConfig{\n\t\t\t\ttbl:   \"ParentTableCascade\",\n\t\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols: []string{\"Id\", \"Value\"},\n\t\t\t\tks: &KeySet{\n\t\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\t\tmakeListValue(makeStringValue(\"100\")),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tchild: &tableConfig{\n\t\t\t\ttbl:   \"InterleavedCascade\",\n\t\t\t\twcols: []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols: []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tks: &KeySet{\n\t\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\t\tmakeListValue(\n\t\t\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectsDeleteError: false,\n\t\t},\n\t\t\"DeleteNoAction\": {\n\t\t\tparent: &tableConfig{\n\t\t\t\ttbl:   \"ParentTableNoAction\",\n\t\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols: []string{\"Id\", \"Value\"},\n\t\t\t\tks: &KeySet{\n\t\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\t\tmakeListValue(makeStringValue(\"100\")),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tchild: &tableConfig{\n\t\t\t\ttbl:   \"InterleavedNoAction\",\n\t\t\t\twcols: []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols: []string{\"InterleavedId\", \"Id\", \"Value\"},\n\t\t\t\tks: &KeySet{\n\t\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\t\tmakeListValue(\n\t\t\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectsDeleteError: true,\n\t\t},\n\t}\n\n\tfor name, tt := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx := context.Background()\n\t\t\tdb := newDatabase()\n\t\t\tses := newSession(db, \"foo\")\n\t\t\tfor _, s := range allSchema {\n\t\t\t\tddls := parseDDL(t, s)\n\t\t\t\tfor _, ddl := range ddls {\n\t\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Insert data\n\n\t\t\tparentListValues := []*structpb.ListValue{\n\t\t\t\t{Values: tt.parent.values},\n\t\t\t}\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tif err := db.Insert(ctx, tx, tt.parent.tbl, tt.parent.wcols, parentListValues); err != nil {\n\t\t\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tlistValues := []*structpb.ListValue{\n\t\t\t\t{Values: tt.child.values},\n\t\t\t}\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tif err := db.Insert(ctx, tx, tt.child.tbl, tt.child.wcols, listValues); err != nil {\n\t\t\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// Delete parent entry\n\n\t\t\tif tt.expectsDeleteError {\n\t\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\t\tif err := db.Delete(ctx, tx, tt.parent.tbl, tt.parent.ks); err == nil {\n\t\t\t\t\t\tt.Fatalf(\"Delete parent succeeded even though it should fail: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tif err := db.Delete(ctx, tx, tt.parent.tbl, tt.parent.ks); err != nil {\n\t\t\t\t\tt.Fatalf(\"Delete parent failed: %v\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// Try to read child entry\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tit, err := db.Read(ctx, tx, tt.child.tbl, \"\", tt.child.cols, tt.child.ks, 1)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Read failed: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tvar rows [][]interface{}\n\t\t\t\terr = it.Do(func(row []interface{}) error {\n\t\t\t\t\trows = append(rows, row)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tif rows != nil {\n\t\t\t\t\tt.Fatalf(\"Child did not get deleted with parent\")\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestInformationSchema(t *testing.T) {\n\tctx := context.Background()\n\tdb := newDatabase()\n\tfor _, s := range allSchema {\n\t\tddls := parseDDL(t, s)\n\t\tfor _, ddl := range ddls {\n\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t}\n\t}\n\n\ttable := []struct {\n\t\tname     string\n\t\tsql      string\n\t\tparams   map[string]Value\n\t\texpected [][]interface{}\n\t\tnames    []string\n\t\tcode     codes.Code\n\t\tmsg      *regexp.Regexp\n\t}{\n\t\t{\n\t\t\tname:  \"Schemata1\",\n\t\t\tsql:   `SELECT * FROM INFORMATION_SCHEMA.SCHEMATA WHERE EFFECTIVE_TIMESTAMP IS NULL`,\n\t\t\tnames: []string{\"CATALOG_NAME\", \"SCHEMA_NAME\", \"EFFECTIVE_TIMESTAMP\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"\", \"INFORMATION_SCHEMA\", nil},\n\t\t\t\t[]interface{}{\"\", \"SPANNER_SYS\", nil},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"Schemata2\",\n\t\t\tsql:   `SELECT CATALOG_NAME, SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE EFFECTIVE_TIMESTAMP IS NOT NULL`,\n\t\t\tnames: []string{\"CATALOG_NAME\", \"SCHEMA_NAME\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"\", \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"Tables_Reserved\",\n\t\t\tsql:   `SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA != \"\" ORDER BY TABLE_SCHEMA, TABLE_NAME`,\n\t\t\tnames: []string{\"TABLE_CATALOG\", \"TABLE_SCHEMA\", \"TABLE_NAME\", \"PARENT_TABLE_NAME\", \"ON_DELETE_ACTION\", \"SPANNER_STATE\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"\", \"INFORMATION_SCHEMA\", \"COLUMNS\", nil, nil, nil},\n\t\t\t\t[]interface{}{\"\", \"INFORMATION_SCHEMA\", \"COLUMN_OPTIONS\", nil, nil, nil},\n\t\t\t\t// []interface{}{\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_COLUMN_USAGE\", nil, nil, nil},\n\t\t\t\t// []interface{}{\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_TABLE_USAGE\", nil, nil, nil},\n\t\t\t\t[]interface{}{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", nil, nil, nil},\n\t\t\t\t[]interface{}{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", nil, nil, nil},\n\t\t\t\t// []interface{}{\"\", \"INFORMATION_SCHEMA\", \"KEY_COLUMN_USAGE\", nil, nil, nil},\n\t\t\t\t[]interface{}{\"\", \"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\", nil, nil, nil},\n\t\t\t\t[]interface{}{\"\", \"INFORMATION_SCHEMA\", \"SCHEMATA\", nil, nil, nil},\n\t\t\t\t// []interface{}{\"\", \"INFORMATION_SCHEMA\", \"TABLE_CONSTRAINTS\", nil, nil, nil},\n\t\t\t\t[]interface{}{\"\", \"INFORMATION_SCHEMA\", \"TABLES\", nil, nil, nil},\n\t\t\t\t// []interface{}{\"\", \"SPANNER_SYS\", \"QUERY_STATS_TOP_10MINUTE\", nil, nil, nil},\n\t\t\t\t// []interface{}{\"\", \"SPANNER_SYS\", \"QUERY_STATS_TOP_HOUR\", nil, nil, nil},\n\t\t\t\t// []interface{}{\"\", \"SPANNER_SYS\", \"QUERY_STATS_TOP_MINUTE\", nil, nil, nil},\n\t\t\t\t// []interface{}{\"\", \"SPANNER_SYS\", \"QUERY_STATS_TOTAL_10MINUTE\", nil, nil, nil},\n\t\t\t\t// []interface{}{\"\", \"SPANNER_SYS\", \"QUERY_STATS_TOTAL_HOUR\", nil, nil, nil},\n\t\t\t\t// []interface{}{\"\", \"SPANNER_SYS\", \"QUERY_STATS_TOTAL_MINUTE\", nil, nil, nil},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"Tables_NonReserved\",\n\t\t\tsql:   `SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = \"\" ORDER BY TABLE_NAME`,\n\t\t\tnames: []string{\"TABLE_CATALOG\", \"TABLE_SCHEMA\", \"TABLE_NAME\", \"PARENT_TABLE_NAME\", \"ON_DELETE_ACTION\", \"SPANNER_STATE\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"\", \"\", \"ArrayTypes\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"CompositePrimaryKeys\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"DefaultValues\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"ForeignChildCascade\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"ForeignChildNoAction\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"ForeignParentCascade\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"ForeignParentNoAction\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"From\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"FullTypes\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"GeneratedColumn\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"GeneratedValues\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"Interleaved\", \"ParentTable\", nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"InterleavedCascade\", \"ParentTableCascade\", \"CASCADE\", \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"InterleavedNoAction\", \"ParentTableNoAction\", \"NO ACTION\", \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"JoinA\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"JoinB\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"ParentTable\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"ParentTableCascade\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"ParentTableNoAction\", nil, nil, \"COMMITTED\"},\n\t\t\t\t[]interface{}{\"\", \"\", \"Simple\", nil, nil, \"COMMITTED\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"Columns_Reserved\",\n\t\t\tsql:   `SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA != \"\" ORDER BY TABLE_NAME, ORDINAL_POSITION`,\n\t\t\tnames: []string{\"TABLE_CATALOG\", \"TABLE_SCHEMA\", \"TABLE_NAME\", \"COLUMN_NAME\", \"ORDINAL_POSITION\", \"COLUMN_DEFAULT\", \"DATA_TYPE\", \"IS_NULLABLE\", \"SPANNER_TYPE\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMNS\", \"TABLE_CATALOG\", int64(1), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMNS\", \"TABLE_SCHEMA\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMNS\", \"TABLE_NAME\", int64(3), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMNS\", \"COLUMN_NAME\", int64(4), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMNS\", \"ORDINAL_POSITION\", int64(5), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMNS\", \"COLUMN_DEFAULT\", int64(6), nil, nil, \"YES\", \"BYTES(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMNS\", \"DATA_TYPE\", int64(7), nil, nil, \"YES\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMNS\", \"IS_NULLABLE\", int64(8), nil, nil, \"YES\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMNS\", \"SPANNER_TYPE\", int64(9), nil, nil, \"YES\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMN_OPTIONS\", \"TABLE_CATALOG\", int64(1), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMN_OPTIONS\", \"TABLE_SCHEMA\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMN_OPTIONS\", \"TABLE_NAME\", int64(3), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMN_OPTIONS\", \"COLUMN_NAME\", int64(4), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMN_OPTIONS\", \"OPTION_NAME\", int64(5), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMN_OPTIONS\", \"OPTION_TYPE\", int64(6), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMN_OPTIONS\", \"OPTION_VALUE\", int64(7), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_COLUMN_USAGE\", \"TABLE_CATALOG\", int64(1), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_COLUMN_USAGE\", \"TABLE_SCHEMA\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_COLUMN_USAGE\", \"TABLE_NAME\", int64(3), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_COLUMN_USAGE\", \"COLUMN_NAME\", int64(4), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_COLUMN_USAGE\", \"CONSTRAINT_CATALOG\", int64(5), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_COLUMN_USAGE\", \"CONSTRAINT_SCHEMA\", int64(6), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_COLUMN_USAGE\", \"CONSTRAINT_NAME\", int64(7), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_TABLE_USAGE\", \"TABLE_CATALOG\", int64(1), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_TABLE_USAGE\", \"TABLE_SCHEMA\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_TABLE_USAGE\", \"TABLE_NAME\", int64(3), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_TABLE_USAGE\", \"CONSTRAINT_CATALOG\", int64(4), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_TABLE_USAGE\", \"CONSTRAINT_SCHEMA\", int64(5), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_TABLE_USAGE\", \"CONSTRAINT_NAME\", int64(6), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"TABLE_CATALOG\", int64(1), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"TABLE_SCHEMA\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"TABLE_NAME\", int64(3), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"INDEX_NAME\", int64(4), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"INDEX_TYPE\", int64(5), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"PARENT_TABLE_NAME\", int64(6), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"IS_UNIQUE\", int64(7), nil, nil, \"NO\", \"BOOL\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"IS_NULL_FILTERED\", int64(8), nil, nil, \"NO\", \"BOOL\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"INDEX_STATE\", int64(9), nil, nil, \"YES\", \"STRING(100)\"}, // TODO\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"SPANNER_IS_MANAGED\", int64(10), nil, nil, \"NO\", \"BOOL\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"TABLE_CATALOG\", int64(1), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"TABLE_SCHEMA\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"TABLE_NAME\", int64(3), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"INDEX_NAME\", int64(4), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"INDEX_TYPE\", int64(5), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"COLUMN_NAME\", int64(6), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"ORDINAL_POSITION\", int64(7), nil, nil, \"YES\", \"INT64\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"COLUMN_ORDERING\", int64(8), nil, nil, \"YES\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"IS_NULLABLE\", int64(9), nil, nil, \"YES\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"SPANNER_TYPE\", int64(10), nil, nil, \"YES\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"KEY_COLUMN_USAGE\", \"CONSTRAINT_CATALOG\", int64(1), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"KEY_COLUMN_USAGE\", \"CONSTRAINT_SCHEMA\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"KEY_COLUMN_USAGE\", \"CONSTRAINT_NAME\", int64(3), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"KEY_COLUMN_USAGE\", \"TABLE_CATALOG\", int64(4), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"KEY_COLUMN_USAGE\", \"TABLE_SCHEMA\", int64(5), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"KEY_COLUMN_USAGE\", \"TABLE_NAME\", int64(6), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"KEY_COLUMN_USAGE\", \"COLUMN_NAME\", int64(7), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"KEY_COLUMN_USAGE\", \"ORDINAL_POSITION\", int64(8), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"KEY_COLUMN_USAGE\", \"POSITION_IN_UNIQUE_CONSTRAINT\", int64(9), nil, nil, \"YES\", \"INT64\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\", \"CONSTRAINT_CATALOG\", int64(1), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\", \"CONSTRAINT_SCHEMA\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\", \"CONSTRAINT_NAME\", int64(3), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\", \"UNIQUE_CONSTRAINT_CATALOG\", int64(4), nil, nil, \"YES\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\", \"UNIQUE_CONSTRAINT_SCHEMA\", int64(5), nil, nil, \"YES\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\", \"UNIQUE_CONSTRAINT_NAME\", int64(6), nil, nil, \"YES\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\", \"MATCH_OPTION\", int64(7), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\", \"UPDATE_RULE\", int64(8), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\", \"DELETE_RULE\", int64(9), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\", \"SPANNER_STATE\", int64(10), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"SCHEMATA\", \"CATALOG_NAME\", int64(1), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"SCHEMATA\", \"SCHEMA_NAME\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"SCHEMATA\", \"EFFECTIVE_TIMESTAMP\", int64(3), nil, nil, \"YES\", \"INT64\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"TABLE_CONSTRAINTS\", \"CONSTRAINT_CATALOG\", int64(1), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"TABLE_CONSTRAINTS\", \"CONSTRAINT_SCHEMA\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"TABLE_CONSTRAINTS\", \"CONSTRAINT_NAME\", int64(3), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"TABLE_CONSTRAINTS\", \"TABLE_CATALOG\", int64(4), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"TABLE_CONSTRAINTS\", \"TABLE_SCHEMA\", int64(5), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"TABLE_CONSTRAINTS\", \"TABLE_NAME\", int64(6), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"TABLE_CONSTRAINTS\", \"CONSTRAINT_TYPE\", int64(7), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"TABLE_CONSTRAINTS\", \"IS_DEFERRABLE\", int64(8), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"TABLE_CONSTRAINTS\", \"INITIALLY_DEFERRED\", int64(9), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"TABLE_CONSTRAINTS\", \"ENFORCED\", int64(10), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"TABLES\", \"TABLE_CATALOG\", int64(1), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"TABLES\", \"TABLE_SCHEMA\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"TABLES\", \"TABLE_NAME\", int64(3), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"TABLES\", \"PARENT_TABLE_NAME\", int64(4), nil, nil, \"YES\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"TABLES\", \"ON_DELETE_ACTION\", int64(5), nil, nil, \"YES\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"TABLES\", \"SPANNER_STATE\", int64(6), nil, nil, \"YES\", \"STRING(MAX)\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"Columns_NonReserved\",\n\t\t\tsql:   `SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = \"\" ORDER BY TABLE_NAME, ORDINAL_POSITION`,\n\t\t\tnames: []string{\"TABLE_CATALOG\", \"TABLE_SCHEMA\", \"TABLE_NAME\", \"COLUMN_NAME\", \"ORDINAL_POSITION\", \"COLUMN_DEFAULT\", \"DATA_TYPE\", \"IS_NULLABLE\", \"SPANNER_TYPE\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t{\"\", \"\", \"ArrayTypes\", \"Id\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ArrayTypes\", \"ArrayString\", int64(2), nil, nil, \"YES\", \"ARRAY<STRING(32)>\"},\n\t\t\t\t{\"\", \"\", \"ArrayTypes\", \"ArrayBool\", int64(3), nil, nil, \"YES\", \"ARRAY<BOOL>\"},\n\t\t\t\t{\"\", \"\", \"ArrayTypes\", \"ArrayBytes\", int64(4), nil, nil, \"YES\", \"ARRAY<BYTES(32)>\"},\n\t\t\t\t{\"\", \"\", \"ArrayTypes\", \"ArrayTimestamp\", int64(5), nil, nil, \"YES\", \"ARRAY<TIMESTAMP>\"},\n\t\t\t\t{\"\", \"\", \"ArrayTypes\", \"ArrayInt\", int64(6), nil, nil, \"YES\", \"ARRAY<INT64>\"},\n\t\t\t\t{\"\", \"\", \"ArrayTypes\", \"ArrayFloat\", int64(7), nil, nil, \"YES\", \"ARRAY<FLOAT64>\"},\n\t\t\t\t{\"\", \"\", \"ArrayTypes\", \"ArrayDate\", int64(8), nil, nil, \"YES\", \"ARRAY<DATE>\"},\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"Id\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"PKey1\", int64(2), nil, nil, \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"PKey2\", int64(3), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"Error\", int64(4), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"X\", int64(5), nil, nil, \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"Y\", int64(6), nil, nil, \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"Z\", int64(7), nil, nil, \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"DefaultValues\", \"Id\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"DefaultValues\", \"Value\", int64(2), nil, nil, \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"DefaultValues\", \"N\", int64(3), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"DefaultValues\", \"X\", int64(4), nil, nil, \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"DefaultValues\", \"Y\", int64(5), nil, nil, \"NO\", \"ARRAY<INT64>\"},\n\t\t\t\t{\"\", \"\", \"DefaultValues\", \"T1\", int64(6), nil, nil, \"NO\", \"TIMESTAMP\"},\n\t\t\t\t{\"\", \"\", \"DefaultValues\", \"T2\", int64(7), nil, nil, \"NO\", \"TIMESTAMP\"},\n\t\t\t\t{\"\", \"\", \"DefaultValues\", \"Date\", int64(8), nil, nil, \"NO\", \"TIMESTAMP\"},\n\t\t\t\t{\"\", \"\", \"ForeignChildCascade\", \"Id\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ForeignChildCascade\", \"ForeignId\", int64(2), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ForeignChildCascade\", \"ForeignSecondId\", int64(3), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ForeignChildCascade\", \"Value\", int64(4), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"\", \"ForeignChildNoAction\", \"Id\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ForeignChildNoAction\", \"ForeignId\", int64(2), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ForeignChildNoAction\", \"ForeignSecondId\", int64(3), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ForeignChildNoAction\", \"Value\", int64(4), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"\", \"ForeignParentCascade\", \"Id\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ForeignParentCascade\", \"Value\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"\", \"ForeignParentNoAction\", \"Id\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ForeignParentNoAction\", \"Value\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"\", \"From\", \"ALL\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"From\", \"CAST\", int64(2), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"From\", \"JOIN\", int64(3), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"PKey\", int64(1), nil, nil, \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FTString\", int64(2), nil, nil, \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FTStringNull\", int64(3), nil, nil, \"YES\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FTBool\", int64(4), nil, nil, \"NO\", \"BOOL\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FTBoolNull\", int64(5), nil, nil, \"YES\", \"BOOL\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FTBytes\", int64(6), nil, nil, \"NO\", \"BYTES(32)\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FTBytesNull\", int64(7), nil, nil, \"YES\", \"BYTES(32)\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FTTimestamp\", int64(8), nil, nil, \"NO\", \"TIMESTAMP\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FTTimestampNull\", int64(9), nil, nil, \"YES\", \"TIMESTAMP\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FTInt\", int64(10), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FTIntNull\", int64(11), nil, nil, \"YES\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FTFloat\", int64(12), nil, nil, \"NO\", \"FLOAT64\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FTFloatNull\", int64(13), nil, nil, \"YES\", \"FLOAT64\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FTDate\", int64(14), nil, nil, \"NO\", \"DATE\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FTDateNull\", int64(15), nil, nil, \"YES\", \"DATE\"},\n\t\t\t\t{\"\", \"\", \"GeneratedColumn\", \"Id\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"GeneratedColumn\", \"Value\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"\", \"GeneratedValues\", \"Id\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"GeneratedValues\", \"Value\", int64(2), nil, nil, \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"GeneratedValues\", \"N\", int64(3), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"GeneratedValues\", \"N2\", int64(4), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"GeneratedValues\", \"N3\", int64(5), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"GeneratedValues\", \"X\", int64(6), nil, nil, \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"GeneratedValues\", \"X2\", int64(7), nil, nil, \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"Interleaved\", \"InterleavedId\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"Interleaved\", \"Id\", int64(2), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"Interleaved\", \"Value\", int64(3), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"\", \"InterleavedCascade\", \"InterleavedId\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"InterleavedCascade\", \"Id\", int64(2), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"InterleavedCascade\", \"Value\", int64(3), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"\", \"InterleavedNoAction\", \"InterleavedId\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"InterleavedNoAction\", \"Id\", int64(2), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"InterleavedNoAction\", \"Value\", int64(3), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"\", \"JoinA\", \"Id\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"JoinA\", \"Value\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"\", \"JoinB\", \"Id\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"JoinB\", \"Value\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"\", \"ParentTable\", \"Id\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ParentTable\", \"Value\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"\", \"ParentTableCascade\", \"Id\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ParentTableCascade\", \"Value\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"\", \"ParentTableNoAction\", \"Id\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ParentTableNoAction\", \"Value\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"\", \"Simple\", \"Id\", int64(1), nil, nil, \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"Simple\", \"Value\", int64(2), nil, nil, \"NO\", \"STRING(MAX)\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"ColumnOptions\",\n\t\t\tsql:   `SELECT * FROM INFORMATION_SCHEMA.COLUMN_OPTIONS ORDER BY TABLE_NAME`,\n\t\t\tnames: []string{\"TABLE_CATALOG\", \"TABLE_SCHEMA\", \"TABLE_NAME\", \"COLUMN_NAME\", \"OPTION_NAME\", \"OPTION_TYPE\", \"OPTION_VALUE\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FTTimestampNull\", \"allow_commit_timestamp\", \"BOOL\", \"TRUE\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"Indexes_ReservedTables\",\n\t\t\tsql:   `SELECT * FROM INFORMATION_SCHEMA.INDEXES WHERE TABLE_SCHEMA != \"\" ORDER BY TABLE_NAME`,\n\t\t\tnames: []string{\"TABLE_CATALOG\", \"TABLE_SCHEMA\", \"TABLE_NAME\", \"INDEX_NAME\", \"INDEX_TYPE\", \"PARENT_TABLE_NAME\", \"IS_UNIQUE\", \"IS_NULL_FILTERED\", \"INDEX_STATE\", \"SPANNER_IS_MANAGED\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMNS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMN_OPTIONS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_COLUMN_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_TABLE_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"KEY_COLUMN_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"SCHEMATA\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"TABLE_CONSTRAINTS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"TABLES\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"Indexes_NonReservedTables_PrimaryKey\",\n\t\t\tsql:   `SELECT * FROM INFORMATION_SCHEMA.INDEXES WHERE TABLE_SCHEMA = \"\" AND INDEX_TYPE = \"PRIMARY_KEY\" ORDER BY TABLE_NAME`,\n\t\t\tnames: []string{\"TABLE_CATALOG\", \"TABLE_SCHEMA\", \"TABLE_NAME\", \"INDEX_NAME\", \"INDEX_TYPE\", \"PARENT_TABLE_NAME\", \"IS_UNIQUE\", \"IS_NULL_FILTERED\", \"INDEX_STATE\", \"SPANNER_IS_MANAGED\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t{\"\", \"\", \"ArrayTypes\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"DefaultValues\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"ForeignChildCascade\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"ForeignChildNoAction\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"ForeignParentCascade\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"ForeignParentNoAction\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"From\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"GeneratedColumn\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"GeneratedValues\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"Interleaved\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"InterleavedCascade\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"InterleavedNoAction\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"JoinA\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"JoinB\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"ParentTable\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"ParentTableCascade\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"ParentTableNoAction\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t\t{\"\", \"\", \"Simple\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"\", true, false, nil, false},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"Indexes_NonReservedTables_Others\",\n\t\t\tsql:   `SELECT * FROM INFORMATION_SCHEMA.INDEXES WHERE TABLE_SCHEMA = \"\" AND INDEX_TYPE != \"PRIMARY_KEY\" ORDER BY TABLE_NAME, INDEX_NAME`,\n\t\t\tnames: []string{\"TABLE_CATALOG\", \"TABLE_SCHEMA\", \"TABLE_NAME\", \"INDEX_NAME\", \"INDEX_TYPE\", \"PARENT_TABLE_NAME\", \"IS_UNIQUE\", \"IS_NULL_FILTERED\", \"INDEX_STATE\", \"SPANNER_IS_MANAGED\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"CompositePrimaryKeysByError\", \"INDEX\", \"\", false, false, \"READ_WRITE\", false},\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"CompositePrimaryKeysByXY\", \"INDEX\", \"\", false, false, \"READ_WRITE\", false},\n\t\t\t\t{\"\", \"\", \"From\", \"ALL\", \"INDEX\", \"\", false, false, \"READ_WRITE\", false},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FullTypesByFTString\", \"INDEX\", \"\", true, false, \"READ_WRITE\", false},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FullTypesByIntDate\", \"INDEX\", \"\", true, false, \"READ_WRITE\", false},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FullTypesByIntTimestamp\", \"INDEX\", \"\", false, false, \"READ_WRITE\", false},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FullTypesByTimestamp\", \"INDEX\", \"\", false, false, \"READ_WRITE\", false},\n\t\t\t\t{\"\", \"\", \"Interleaved\", \"InterleavedKey\", \"INDEX\", \"ParentTable\", false, false, \"READ_WRITE\", false},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"IndexColumns_ReservedTables\",\n\t\t\tsql:   `SELECT * FROM INFORMATION_SCHEMA.INDEX_COLUMNS WHERE TABLE_SCHEMA != \"\" ORDER BY TABLE_NAME, INDEX_NAME, ORDINAL_POSITION`,\n\t\t\tnames: []string{\"TABLE_CATALOG\", \"TABLE_SCHEMA\", \"TABLE_NAME\", \"INDEX_NAME\", \"INDEX_TYPE\", \"COLUMN_NAME\", \"ORDINAL_POSITION\", \"COLUMN_ORDERING\", \"IS_NULLABLE\", \"SPANNER_TYPE\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMNS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_CATALOG\", int64(1), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMNS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_SCHEMA\", int64(2), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMNS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_NAME\", int64(3), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMNS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"COLUMN_NAME\", int64(4), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMN_OPTIONS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_CATALOG\", int64(1), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMN_OPTIONS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_SCHEMA\", int64(2), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMN_OPTIONS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_NAME\", int64(3), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMN_OPTIONS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"COLUMN_NAME\", int64(4), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"COLUMN_OPTIONS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"OPTION_NAME\", int64(5), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_COLUMN_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CONSTRAINT_CATALOG\", int64(1), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_COLUMN_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CONSTRAINT_SCHEMA\", int64(2), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_COLUMN_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CONSTRAINT_NAME\", int64(3), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_COLUMN_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"COLUMN_NAME\", int64(4), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_TABLE_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_CATALOG\", int64(1), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_TABLE_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_SCHEMA\", int64(2), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_TABLE_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_NAME\", int64(3), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_TABLE_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CONSTRAINT_CATALOG\", int64(4), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_TABLE_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CONSTRAINT_SCHEMA\", int64(5), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"CONSTRAINT_TABLE_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CONSTRAINT_NAME\", int64(6), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_CATALOG\", int64(1), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_SCHEMA\", int64(2), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_NAME\", int64(3), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"INDEX_NAME\", int64(4), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEXES\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"INDEX_TYPE\", int64(5), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_CATALOG\", int64(1), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_SCHEMA\", int64(2), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_NAME\", int64(3), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"INDEX_NAME\", int64(4), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"INDEX_TYPE\", int64(5), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"COLUMN_NAME\", int64(6), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"KEY_COLUMN_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CONSTRAINT_CATALOG\", int64(1), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"KEY_COLUMN_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CONSTRAINT_SCHEMA\", int64(2), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"KEY_COLUMN_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CONSTRAINT_NAME\", int64(3), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"KEY_COLUMN_USAGE\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"COLUMN_NAME\", int64(4), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CONSTRAINT_CATALOG\", int64(1), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CONSTRAINT_SCHEMA\", int64(2), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CONSTRAINT_NAME\", int64(3), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"SCHEMATA\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CATALOG_NAME\", int64(1), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"SCHEMATA\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"SCHEMA_NAME\", int64(2), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"TABLE_CONSTRAINTS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CONSTRAINT_CATALOG\", int64(1), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"TABLE_CONSTRAINTS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CONSTRAINT_SCHEMA\", int64(2), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t// {\"\", \"INFORMATION_SCHEMA\", \"TABLE_CONSTRAINTS\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"CONSTRAINT_NAME\", int64(3), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"TABLES\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_CATALOG\", int64(1), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"TABLES\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_SCHEMA\", int64(2), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"INFORMATION_SCHEMA\", \"TABLES\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"TABLE_NAME\", int64(3), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"IndexColumns_NonReservedTables\",\n\t\t\tsql:   `SELECT * FROM INFORMATION_SCHEMA.INDEX_COLUMNS WHERE TABLE_SCHEMA = \"\" ORDER BY TABLE_NAME, INDEX_NAME, ORDINAL_POSITION`,\n\t\t\tnames: []string{\"TABLE_CATALOG\", \"TABLE_SCHEMA\", \"TABLE_NAME\", \"INDEX_NAME\", \"INDEX_TYPE\", \"COLUMN_NAME\", \"ORDINAL_POSITION\", \"COLUMN_ORDERING\", \"IS_NULLABLE\", \"SPANNER_TYPE\"},\n\t\t\texpected: [][]interface{}{\n\t\t\t\t{\"\", \"\", \"ArrayTypes\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"CompositePrimaryKeysByError\", \"INDEX\", \"Error\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"CompositePrimaryKeysByXY\", \"INDEX\", \"Z\", nil, nil, \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"CompositePrimaryKeysByXY\", \"INDEX\", \"X\", int64(1), \"ASC\", \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"CompositePrimaryKeysByXY\", \"INDEX\", \"Y\", int64(2), \"DESC\", \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"PKey1\", int64(1), \"ASC\", \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"CompositePrimaryKeys\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"PKey2\", int64(2), \"DESC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"DefaultValues\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ForeignChildCascade\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ForeignChildCascade\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"ForeignId\", int64(2), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ForeignChildNoAction\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ForeignChildNoAction\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"ForeignId\", int64(2), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ForeignParentCascade\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ForeignParentNoAction\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"From\", \"ALL\", \"INDEX\", \"ALL\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"From\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"ALL\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FullTypesByFTString\", \"INDEX\", \"FTString\", int64(1), \"ASC\", \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FullTypesByIntDate\", \"INDEX\", \"FTInt\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FullTypesByIntDate\", \"INDEX\", \"FTDate\", int64(2), \"ASC\", \"NO\", \"DATE\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FullTypesByIntTimestamp\", \"INDEX\", \"FTInt\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FullTypesByIntTimestamp\", \"INDEX\", \"FTTimestamp\", int64(2), \"ASC\", \"NO\", \"TIMESTAMP\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"FullTypesByTimestamp\", \"INDEX\", \"FTTimestamp\", int64(1), \"ASC\", \"NO\", \"TIMESTAMP\"},\n\t\t\t\t{\"\", \"\", \"FullTypes\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"PKey\", int64(1), \"ASC\", \"NO\", \"STRING(32)\"},\n\t\t\t\t{\"\", \"\", \"GeneratedColumn\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"GeneratedValues\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"Interleaved\", \"InterleavedKey\", \"INDEX\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"Interleaved\", \"InterleavedKey\", \"INDEX\", \"Value\", int64(2), \"ASC\", \"NO\", \"STRING(MAX)\"},\n\t\t\t\t{\"\", \"\", \"Interleaved\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"Interleaved\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"InterleavedId\", int64(2), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"InterleavedCascade\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"InterleavedCascade\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"InterleavedId\", int64(2), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"InterleavedNoAction\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"InterleavedNoAction\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"InterleavedId\", int64(2), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"JoinA\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"JoinB\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ParentTable\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ParentTableCascade\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"ParentTableNoAction\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t\t{\"\", \"\", \"Simple\", \"PRIMARY_KEY\", \"PRIMARY_KEY\", \"Id\", int64(1), \"ASC\", \"NO\", \"INT64\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"IndexColumns_WithAlias\",\n\t\t\tsql:  `SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.INDEX_COLUMNS a WHERE a.TABLE_NAME = \"Simple\"`,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"Id\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"IndexColumns_WithOmittedSchema\",\n\t\t\tsql:  `SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.INDEX_COLUMNS WHERE INDEX_COLUMNS.TABLE_NAME = \"Simple\"`,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"Id\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"IndexColumns_WithJoin\",\n\t\t\tsql:  `SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.INDEX_COLUMNS LEFT JOIN INFORMATION_SCHEMA.INDEXES ON INDEX_COLUMNS.TABLE_NAME = INDEXES.TABLE_NAME WHERE INDEX_COLUMNS.TABLE_NAME = \"Simple\"`,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"Id\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"IndexColumns_WithUsingJoin\",\n\t\t\tsql:  `SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.INDEX_COLUMNS LEFT JOIN INFORMATION_SCHEMA.INDEXES USING(TABLE_NAME) WHERE INDEX_COLUMNS.TABLE_NAME = \"Simple\"`,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t[]interface{}{\"Id\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"ReferentialConstraints\",\n\t\t\tsql:  `SELECT CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, UNIQUE_CONSTRAINT_CATALOG, UNIQUE_CONSTRAINT_SCHEMA, UNIQUE_CONSTRAINT_NAME, MATCH_OPTION, UPDATE_RULE, DELETE_RULE, SPANNER_STATE FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS`,\n\t\t\texpected: [][]interface{}{\n\t\t\t\t{\"\", \"\", \"FK_Cascade\", \"\", \"\", \"ForeignParentCascade\", \"SIMPLE\", \"NO ACTION\", \"CASCADE\", \"COMMITTED\"},\n\t\t\t\t{\"\", \"\", \"FK_ForeignChildCascade_ForeignParentCascade_1\", \"\", \"\", \"ForeignParentCascade\", \"SIMPLE\", \"NO ACTION\", \"CASCADE\", \"COMMITTED\"},\n\t\t\t\t{\"\", \"\", \"FK_NoAction\", \"\", \"\", \"ForeignParentNoAction\", \"SIMPLE\", \"NO ACTION\", \"NO ACTION\", \"COMMITTED\"},\n\t\t\t\t{\"\", \"\", \"FK_ForeignChildNoAction_ForeignParentNoAction_1\", \"\", \"\", \"ForeignParentNoAction\", \"SIMPLE\", \"NO ACTION\", \"NO ACTION\", \"COMMITTED\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range table {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tses := newSession(db, \"foo\")\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tstmt, err := (&memefish.Parser{\n\t\t\t\t\tLexer: &memefish.Lexer{\n\t\t\t\t\t\tFile: &token.File{FilePath: \"\", Buffer: tc.sql},\n\t\t\t\t\t},\n\t\t\t\t}).ParseQuery()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"failed to parse sql: %q %v\", tc.sql, err)\n\t\t\t\t}\n\n\t\t\t\t// The test case expects OK, it checks respons values.\n\t\t\t\t// otherwise it checks the error code and the error message.\n\t\t\t\tif tc.code == codes.OK {\n\t\t\t\t\tit, err := db.Query(ctx, tx, stmt, tc.params)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"Query failed: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\tvar rows [][]interface{}\n\t\t\t\t\terr = it.Do(func(row []interface{}) error {\n\t\t\t\t\t\trows = append(rows, row)\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\tif diff := cmp.Diff(tc.expected, rows); diff != \"\" {\n\t\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t\t}\n\n\t\t\t\t\t// TODO: add names to all test cases. now this is optional check\n\t\t\t\t\tif tc.names != nil {\n\t\t\t\t\t\tvar gotnames []string\n\t\t\t\t\t\tfor _, item := range it.ResultSet() {\n\t\t\t\t\t\t\tgotnames = append(gotnames, item.Name)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif diff := cmp.Diff(tc.names, gotnames); diff != \"\" {\n\t\t\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tit, err := db.Query(ctx, tx, stmt, tc.params)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\terr = it.Do(func([]interface{}) error {\n\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tst := status.Convert(err)\n\t\t\t\t\tif st.Code() != tc.code {\n\t\t\t\t\t\tt.Errorf(\"expect code to be %v but got %v\", tc.code, st.Code())\n\t\t\t\t\t}\n\t\t\t\t\tif !tc.msg.MatchString(st.Message()) {\n\t\t\t\t\t\tt.Errorf(\"unexpected error message: \\n %q\\n expected:\\n %q\", st.Message(), tc.msg)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestInsertUnderForeignConstraint(t *testing.T) {\n\ttype tableConfig struct {\n\t\ttbl      string\n\t\twcols    []string\n\t\tvalues   []*structpb.Value\n\t\tcols     []string\n\t\tlimit    int64\n\t\texpected [][]interface{}\n\t}\n\n\ttable := map[string]struct {\n\t\tparent       *tableConfig\n\t\tchild        *tableConfig\n\t\texpectsError bool\n\t}{\n\t\t\"InsertWithoutMaster\": {\n\t\t\tchild: &tableConfig{\n\t\t\t\ttbl:   \"ForeignChildNoAction\",\n\t\t\t\twcols: []string{\"Id\", \"ForeignId\", \"ForeignSecondId\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t\t},\n\t\t\t\tcols:     []string{\"Id\", \"ForeignId\", \"ForeignSecondId\", \"Value\"},\n\t\t\t\tlimit:    100,\n\t\t\t\texpected: nil,\n\t\t\t},\n\t\t\texpectsError: true,\n\t\t},\n\t\t\"InsertWithParent\": {\n\t\t\tparent: &tableConfig{\n\t\t\t\ttbl:   \"ForeignParentNoAction\",\n\t\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\t\tlimit: 100,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t{int64(200), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tchild: &tableConfig{\n\t\t\t\ttbl:   \"ForeignChildNoAction\",\n\t\t\t\twcols: []string{\"Id\", \"ForeignId\", \"ForeignSecondId\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t\t},\n\t\t\t\tcols:  []string{\"Id\", \"ForeignId\", \"ForeignSecondId\", \"Value\"},\n\t\t\t\tlimit: 100,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t{int64(100), int64(200), int64(200), \"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectsError: false,\n\t\t},\n\n\t\t\"InsertWithoutMaster(Cascade)\": {\n\t\t\tchild: &tableConfig{\n\t\t\t\ttbl:   \"ForeignChildCascade\",\n\t\t\t\twcols: []string{\"Id\", \"ForeignId\", \"ForeignSecondId\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t\t},\n\t\t\t\tcols:     []string{\"Id\", \"ForeignId\", \"ForeignSecondId\", \"Value\"},\n\t\t\t\tlimit:    100,\n\t\t\t\texpected: nil,\n\t\t\t},\n\t\t\texpectsError: true,\n\t\t},\n\t\t\"InsertWithParent(Cascade)\": {\n\t\t\tparent: &tableConfig{\n\t\t\t\ttbl:   \"ForeignParentCascade\",\n\t\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols:  []string{\"Id\", \"Value\"},\n\t\t\t\tlimit: 100,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t{int64(200), \"xxx\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tchild: &tableConfig{\n\t\t\t\ttbl:   \"ForeignChildCascade\",\n\t\t\t\twcols: []string{\"Id\", \"ForeignId\", \"ForeignSecondId\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t\t},\n\t\t\t\tcols:  []string{\"Id\", \"ForeignId\", \"ForeignSecondId\", \"Value\"},\n\t\t\t\tlimit: 100,\n\t\t\t\texpected: [][]interface{}{\n\t\t\t\t\t{int64(100), int64(200), int64(200), \"yyy\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectsError: false,\n\t\t},\n\t}\n\n\tfor name, tt := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx := context.Background()\n\t\t\tdb := newDatabase()\n\t\t\tses := newSession(db, \"foo\")\n\t\t\tfor _, s := range allSchema {\n\t\t\t\tddls := parseDDL(t, s)\n\t\t\t\tfor _, ddl := range ddls {\n\t\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif tt.parent != nil {\n\t\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\t\ttestInsertHelper(t, ctx, db, tx, tt.parent.tbl, tt.parent.cols, tt.parent.wcols, tt.parent.values, tt.parent.limit, tt.parent.expected)\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tif tt.expectsError {\n\t\t\t\tlistValues := []*structpb.ListValue{\n\t\t\t\t\t{Values: tt.child.values},\n\t\t\t\t}\n\t\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\t\tif err := db.Insert(ctx, tx, tt.child.tbl, tt.child.wcols, listValues); err == nil {\n\t\t\t\t\t\tt.Fatalf(\"Insert succeeded even though it should fail: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\ttestInsertHelper(t, ctx, db, tx, tt.child.tbl, tt.child.cols, tt.child.wcols, tt.child.values, tt.child.limit, tt.child.expected)\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestDeleteUnderForeignConstraint(t *testing.T) {\n\ttype tableConfig struct {\n\t\ttbl    string\n\t\twcols  []string\n\t\tvalues []*structpb.Value\n\t\tcols   []string\n\t\tks     *KeySet\n\t}\n\n\ttable := map[string]struct {\n\t\tchild              *tableConfig\n\t\tparent             *tableConfig\n\t\texpectsDeleteError bool\n\t}{\n\t\t\"DeleteNoAction\": {\n\t\t\tparent: &tableConfig{\n\t\t\t\ttbl:   \"ForeignParentNoAction\",\n\t\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols: []string{\"Id\", \"Value\"},\n\t\t\t\tks: &KeySet{\n\t\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\t\tmakeListValue(makeStringValue(\"200\")),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tchild: &tableConfig{\n\t\t\t\ttbl:   \"ForeignChildNoAction\",\n\t\t\t\twcols: []string{\"Id\", \"ForeignId\", \"ForeignSecondId\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t\t},\n\t\t\t\tcols: []string{\"Id\", \"ForeignId\", \"ForeignSecondId\", \"Value\"},\n\t\t\t\tks: &KeySet{\n\t\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\t\tmakeListValue(\n\t\t\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectsDeleteError: true,\n\t\t},\n\t\t\"DeleteCascade\": {\n\t\t\tparent: &tableConfig{\n\t\t\t\ttbl:   \"ForeignParentCascade\",\n\t\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t},\n\t\t\t\tcols: []string{\"Id\", \"Value\"},\n\t\t\t\tks: &KeySet{\n\t\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\t\tmakeListValue(makeStringValue(\"200\")),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tchild: &tableConfig{\n\t\t\t\ttbl:   \"ForeignChildCascade\",\n\t\t\t\twcols: []string{\"Id\", \"ForeignId\", \"ForeignSecondId\", \"Value\"},\n\t\t\t\tvalues: []*structpb.Value{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t\t},\n\t\t\t\tcols: []string{\"Id\", \"ForeignId\", \"ForeignSecondId\", \"Value\"},\n\t\t\t\tks: &KeySet{\n\t\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\t\tmakeListValue(\n\t\t\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectsDeleteError: false,\n\t\t},\n\t}\n\n\tfor name, tt := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx := context.Background()\n\t\t\tdb := newDatabase()\n\t\t\tses := newSession(db, \"foo\")\n\t\t\tfor _, s := range allSchema {\n\t\t\t\tddls := parseDDL(t, s)\n\t\t\t\tfor _, ddl := range ddls {\n\t\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Insert data\n\n\t\t\tparentListValues := []*structpb.ListValue{\n\t\t\t\t{Values: tt.parent.values},\n\t\t\t}\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tif err := db.Insert(ctx, tx, tt.parent.tbl, tt.parent.wcols, parentListValues); err != nil {\n\t\t\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tlistValues := []*structpb.ListValue{\n\t\t\t\t{Values: tt.child.values},\n\t\t\t}\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tif err := db.Insert(ctx, tx, tt.child.tbl, tt.child.wcols, listValues); err != nil {\n\t\t\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// Delete parent entry\n\n\t\t\tif tt.expectsDeleteError {\n\t\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\t\tif err := db.Delete(ctx, tx, tt.parent.tbl, tt.parent.ks); err == nil {\n\t\t\t\t\t\tt.Fatalf(\"Delete parent succeeded even though it should fail: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tif err := db.Delete(ctx, tx, tt.parent.tbl, tt.parent.ks); err != nil {\n\t\t\t\t\tt.Fatalf(\"Delete parent failed: %v\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// Try to read child entry\n\n\t\t\ttestRunInTransaction(t, ses, func(tx *transaction) {\n\t\t\t\tit, err := db.Read(ctx, tx, tt.child.tbl, \"\", tt.child.cols, tt.child.ks, 1)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Read failed: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tvar rows [][]interface{}\n\t\t\t\terr = it.Do(func(row []interface{}) error {\n\t\t\t\t\trows = append(rows, row)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tif rows != nil {\n\t\t\t\t\tt.Fatalf(\"Child did not get deleted with parent\")\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/debug.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"fmt\"\n)\n\nfunc Debugf(format string, args ...interface{}) {\n\tif IsDebug() {\n\t\tfmt.Printf(format, args...)\n\t}\n}\n\nfunc IsDebug() bool {\n\treturn false\n}\n\nfunc DebugStartEnd(format string, args ...interface{}) func() {\n\tfmt.Printf(format+\" start\\n\", args...)\n\treturn func() {\n\t\tfmt.Printf(format+\" end\\n\", args...)\n\t}\n}\n"
  },
  {
    "path": "server/keyset.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\tspannerpb \"google.golang.org/genproto/googleapis/spanner/v1\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tstructpb \"google.golang.org/protobuf/types/known/structpb\"\n)\n\ntype KeySet struct {\n\tAll    bool\n\tKeys   []*structpb.ListValue\n\tRanges []*KeyRange\n}\n\ntype KeyRange struct {\n\tstart       *structpb.ListValue\n\tend         *structpb.ListValue\n\tstartClosed bool\n\tendClosed   bool\n}\n\nfunc makeKeySet(set *spannerpb.KeySet) *KeySet {\n\tranges := make([]*KeyRange, 0, len(set.Ranges))\n\tfor _, r := range set.Ranges {\n\t\tranges = append(ranges, makeKeyRange(r))\n\t}\n\treturn &KeySet{\n\t\tAll:    set.All,\n\t\tKeys:   set.Keys,\n\t\tRanges: ranges,\n\t}\n}\n\nfunc makeKeyRange(r *spannerpb.KeyRange) *KeyRange {\n\tvar kr KeyRange\n\tswitch s := r.StartKeyType.(type) {\n\tcase *spannerpb.KeyRange_StartClosed:\n\t\tkr.start = s.StartClosed\n\t\tkr.startClosed = true\n\tcase *spannerpb.KeyRange_StartOpen:\n\t\tkr.start = s.StartOpen\n\t}\n\tswitch e := r.EndKeyType.(type) {\n\tcase *spannerpb.KeyRange_EndClosed:\n\t\tkr.end = e.EndClosed\n\t\tkr.endClosed = true\n\tcase *spannerpb.KeyRange_EndOpen:\n\t\tkr.end = e.EndOpen\n\t}\n\treturn &kr\n}\n\nfunc buildWhereClauseFromKeySet(keyset *KeySet, indexColumnsName string, indexColumns []*Column, indexColumnDirs []string) (string, []interface{}, error) {\n\tif keyset.All {\n\t\treturn \"\", nil, nil\n\t}\n\n\tvar conditions []string\n\tvar subargs [][]interface{}\n\n\tif len(keyset.Keys) != 0 {\n\t\tq, a, err := buildKeySetQuery(indexColumnsName, indexColumns, keyset.Keys)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\t\tconditions = append(conditions, q)\n\t\tsubargs = append(subargs, a)\n\t}\n\n\tif len(keyset.Ranges) != 0 {\n\t\tfor _, keyrange := range keyset.Ranges {\n\t\t\tq, a, err := buildKeyRangeQuery(indexColumnsName, indexColumns, indexColumnDirs, keyrange)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", nil, err\n\t\t\t}\n\n\t\t\tconditions = append(conditions, q)\n\t\t\tsubargs = append(subargs, a)\n\t\t}\n\t}\n\n\tcond := strings.Join(conditions, \" OR \")\n\tvar args []interface{}\n\tfor i := range subargs {\n\t\targs = append(args, subargs[i]...)\n\t}\n\n\treturn fmt.Sprintf(\"WHERE %s\", cond), args, nil\n}\n\nfunc buildKeySetQuery(pkeysName string, pkeyColumns []*Column, keys []*structpb.ListValue) (string, []interface{}, error) {\n\tnumPKeys := len(pkeyColumns)\n\targs := make([]interface{}, 0, len(keys)*numPKeys)\n\n\tfor _, key := range keys {\n\t\tif len(key.Values) != numPKeys {\n\t\t\treturn \"\", nil, status.Errorf(codes.InvalidArgument, \"TODO: invalid keys\")\n\t\t}\n\n\t\tvalues, err := convertToDatabaseValues(key, pkeyColumns)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\n\t\targs = append(args, values...)\n\t}\n\n\t// build placeholders for values e.g. (?, ?, ?)\n\tvaluesPlaceholder := \"(?\" + strings.Repeat(\", ?\", numPKeys-1) + \")\"\n\n\t// repeat placeholders for the number of keys e.g. (?, ?, ?), (?, ?, ?), (?, ?, ?)\n\tvaluesExpr := valuesPlaceholder + strings.Repeat(\", \"+valuesPlaceholder, len(keys)-1)\n\n\t// e.g. WHERE (key1, key2, key3) IN ( VALUES (?, ?, ?), (?, ?, ?) )\n\twhereCondition := fmt.Sprintf(\"(%s) IN ( VALUES %s )\", pkeysName, valuesExpr)\n\n\treturn whereCondition, args, nil\n}\n\nfunc buildKeyRangeQuery(pkeysName string, pkeyColumns []*Column, indexColumnDirs []string, keyrange *KeyRange) (string, []interface{}, error) {\n\tnumPKeys := len(pkeyColumns)\n\tif numPKeys < len(keyrange.start.Values) {\n\t\treturn \"\", nil, status.Errorf(codes.InvalidArgument, \"TODO: invalid start range key\")\n\t}\n\tif numPKeys < len(keyrange.end.Values) {\n\t\treturn \"\", nil, status.Errorf(codes.InvalidArgument, \"TODO: invalid end range key\")\n\t}\n\n\tstartKeyValues, err := convertToDatabaseValues(keyrange.start, pkeyColumns)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tendKeyValues, err := convertToDatabaseValues(keyrange.end, pkeyColumns)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\twhereClause := make([]string, 0, len(startKeyValues)+len(endKeyValues))\n\targs := make([]interface{}, 0, len(whereClause))\n\n\tvar maxLen int\n\tif len(startKeyValues) > len(endKeyValues) {\n\t\tmaxLen = len(startKeyValues)\n\t} else {\n\t\tmaxLen = len(endKeyValues)\n\t}\n\tfor i := 0; i < maxLen; i++ {\n\t\tpk := QuoteString(pkeyColumns[i].Name())\n\t\tif len(startKeyValues) > i && len(endKeyValues) > i && startKeyValues[i] == endKeyValues[i] {\n\t\t\twhereClause = append(whereClause, fmt.Sprintf(\"%s = ?\", pk))\n\t\t\targs = append(args, startKeyValues[i])\n\t\t\tcontinue\n\t\t}\n\t\tif len(startKeyValues) > i {\n\t\t\tif indexColumnDirs[i] == \"DESC\" {\n\t\t\t\tif keyrange.startClosed {\n\t\t\t\t\twhereClause = append(whereClause, fmt.Sprintf(\"%s <= ?\", pk))\n\t\t\t\t} else {\n\t\t\t\t\twhereClause = append(whereClause, fmt.Sprintf(\"%s < ?\", pk))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif keyrange.startClosed {\n\t\t\t\t\twhereClause = append(whereClause, fmt.Sprintf(\"%s >= ?\", pk))\n\t\t\t\t} else {\n\t\t\t\t\twhereClause = append(whereClause, fmt.Sprintf(\"%s > ?\", pk))\n\t\t\t\t}\n\t\t\t}\n\t\t\targs = append(args, startKeyValues[i])\n\t\t}\n\t\tif len(endKeyValues) > i {\n\t\t\tif indexColumnDirs[i] == \"DESC\" {\n\t\t\t\tif keyrange.endClosed {\n\t\t\t\t\twhereClause = append(whereClause, fmt.Sprintf(\"%s >= ?\", pk))\n\t\t\t\t} else {\n\t\t\t\t\twhereClause = append(whereClause, fmt.Sprintf(\"%s > ?\", pk))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif keyrange.endClosed {\n\t\t\t\t\twhereClause = append(whereClause, fmt.Sprintf(\"%s <= ?\", pk))\n\t\t\t\t} else {\n\t\t\t\t\twhereClause = append(whereClause, fmt.Sprintf(\"%s < ?\", pk))\n\t\t\t\t}\n\t\t\t}\n\t\t\targs = append(args, endKeyValues[i])\n\t\t}\n\t}\n\n\treturn fmt.Sprintf(\"(%s)\", strings.Join(whereClause, \" AND \")), args, nil\n}\n"
  },
  {
    "path": "server/meta_schema.go",
    "content": "// Copyright 2020 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"github.com/cloudspannerecosystem/memefish/ast\"\n)\n\nvar metaTablesMap = map[string]string{\n\t\"INFORMATION_SCHEMA.SCHEMATA\":                \"__INFORMATION_SCHEMA__SCHEMATA\",\n\t\"INFORMATION_SCHEMA.TABLES\":                  \"__INFORMATION_SCHEMA__TABLES\",\n\t\"INFORMATION_SCHEMA.COLUMNS\":                 \"__INFORMATION_SCHEMA__COLUMNS\",\n\t\"INFORMATION_SCHEMA.INDEXES\":                 \"__INFORMATION_SCHEMA__INDEXES\",\n\t\"INFORMATION_SCHEMA.INDEX_COLUMNS\":           \"__INFORMATION_SCHEMA__INDEX_COLUMNS\",\n\t\"INFORMATION_SCHEMA.COLUMN_OPTIONS\":          \"__INFORMATION_SCHEMA__COLUMN_OPTIONS\",\n\t\"INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS\": \"__INFORMATION_SCHEMA__REFERENTIAL_CONSTRAINTS\",\n}\n\nvar metaTablesReverseMap = map[string][]string{\n\t\"__INFORMATION_SCHEMA__SCHEMATA\":                {\"INFORMATION_SCHEMA\", \"SCHEMATA\"},\n\t\"__INFORMATION_SCHEMA__TABLES\":                  {\"INFORMATION_SCHEMA\", \"TABLES\"},\n\t\"__INFORMATION_SCHEMA__COLUMNS\":                 {\"INFORMATION_SCHEMA\", \"COLUMNS\"},\n\t\"__INFORMATION_SCHEMA__INDEXES\":                 {\"INFORMATION_SCHEMA\", \"INDEXES\"},\n\t\"__INFORMATION_SCHEMA__INDEX_COLUMNS\":           {\"INFORMATION_SCHEMA\", \"INDEX_COLUMNS\"},\n\t\"__INFORMATION_SCHEMA__COLUMN_OPTIONS\":          {\"INFORMATION_SCHEMA\", \"COLUMN_OPTIONS\"},\n\t\"__INFORMATION_SCHEMA__REFERENTIAL_CONSTRAINTS\": {\"INFORMATION_SCHEMA\", \"REFERENTIAL_CONSTRAINTS\"},\n}\n\nvar metaTables = []*ast.CreateTable{\n\t{\n\t\tName: &ast.Path{Idents: []*ast.Ident{{Name: \"__INFORMATION_SCHEMA__SCHEMATA\"}}},\n\t\tColumns: []*ast.ColumnDef{\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"CATALOG_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"SCHEMA_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"EFFECTIVE_TIMESTAMP\"},\n\t\t\t\tType:    &ast.ScalarSchemaType{Name: ast.Int64TypeName},\n\t\t\t\tNotNull: false,\n\t\t\t},\n\t\t},\n\t\tPrimaryKeys: []*ast.IndexKey{\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"CATALOG_NAME\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"SCHEMA_NAME\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tName: &ast.Path{Idents: []*ast.Ident{{Name: \"__INFORMATION_SCHEMA__TABLES\"}}},\n\t\tColumns: []*ast.ColumnDef{\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"TABLE_CATALOG\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"TABLE_SCHEMA\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"TABLE_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"PARENT_TABLE_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"ON_DELETE_ACTION\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"SPANNER_STATE\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: false,\n\t\t\t},\n\t\t},\n\t\tPrimaryKeys: []*ast.IndexKey{\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"TABLE_CATALOG\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"TABLE_SCHEMA\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"TABLE_NAME\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tName: &ast.Path{Idents: []*ast.Ident{{Name: \"__INFORMATION_SCHEMA__COLUMNS\"}}},\n\t\tColumns: []*ast.ColumnDef{\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"TABLE_CATALOG\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"TABLE_SCHEMA\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"TABLE_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"COLUMN_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"ORDINAL_POSITION\"},\n\t\t\t\tType:    &ast.ScalarSchemaType{Name: ast.Int64TypeName},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"COLUMN_DEFAULT\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.BytesTypeName, Max: true},\n\t\t\t\tNotNull: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"DATA_TYPE\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"IS_NULLABLE\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"SPANNER_TYPE\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: false,\n\t\t\t},\n\t\t},\n\t\tPrimaryKeys: []*ast.IndexKey{\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"TABLE_CATALOG\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"TABLE_SCHEMA\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"TABLE_NAME\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"COLUMN_NAME\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tName: &ast.Path{Idents: []*ast.Ident{{Name: \"__INFORMATION_SCHEMA__INDEXES\"}}},\n\t\tColumns: []*ast.ColumnDef{\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"TABLE_CATALOG\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"TABLE_SCHEMA\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"TABLE_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"INDEX_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"INDEX_TYPE\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"PARENT_TABLE_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"IS_UNIQUE\"},\n\t\t\t\tType:    &ast.ScalarSchemaType{Name: ast.BoolTypeName},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"IS_NULL_FILTERED\"},\n\t\t\t\tType:    &ast.ScalarSchemaType{Name: ast.BoolTypeName},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"INDEX_STATE\"},\n\t\t\t\tType: &ast.SizedSchemaType{Name: ast.StringTypeName, Size: &ast.IntLiteral{Value: \"100\", Base: 10}},\n\t\t\t\t// NotNull: true,\n\t\t\t\tNotNull: false, // INFORMATION_SCHEMA.COLUMNS reports this column is non-nullable, but it returns NULL...\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"SPANNER_IS_MANAGED\"},\n\t\t\t\tType:    &ast.ScalarSchemaType{Name: ast.BoolTypeName},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t},\n\t\tPrimaryKeys: []*ast.IndexKey{\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"TABLE_CATALOG\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"TABLE_SCHEMA\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"TABLE_NAME\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"INDEX_NAME\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"INDEX_TYPE\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tName: &ast.Path{Idents: []*ast.Ident{{Name: \"__INFORMATION_SCHEMA__INDEX_COLUMNS\"}}},\n\t\tColumns: []*ast.ColumnDef{\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"TABLE_CATALOG\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"TABLE_SCHEMA\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"TABLE_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"INDEX_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"INDEX_TYPE\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"COLUMN_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"ORDINAL_POSITION\"},\n\t\t\t\tType:    &ast.ScalarSchemaType{Name: ast.Int64TypeName},\n\t\t\t\tNotNull: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"COLUMN_ORDERING\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"IS_NULLABLE\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"SPANNER_TYPE\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: false,\n\t\t\t},\n\t\t},\n\t\tPrimaryKeys: []*ast.IndexKey{\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"TABLE_CATALOG\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"TABLE_SCHEMA\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"TABLE_NAME\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"INDEX_NAME\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"INDEX_TYPE\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"COLUMN_NAME\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t},\n\t},\n\n\t{\n\t\tName: &ast.Path{Idents: []*ast.Ident{{Name: \"__INFORMATION_SCHEMA__COLUMN_OPTIONS\"}}},\n\t\tColumns: []*ast.ColumnDef{\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"TABLE_CATALOG\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"TABLE_SCHEMA\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"TABLE_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"COLUMN_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"OPTION_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"OPTION_TYPE\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"OPTION_VALUE\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t},\n\t\tPrimaryKeys: []*ast.IndexKey{\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"TABLE_CATALOG\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"TABLE_SCHEMA\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"TABLE_NAME\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"COLUMN_NAME\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"OPTION_NAME\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t},\n\t},\n\n\t{\n\t\tName: &ast.Path{Idents: []*ast.Ident{{Name: \"__INFORMATION_SCHEMA__REFERENTIAL_CONSTRAINTS\"}}},\n\t\tColumns: []*ast.ColumnDef{\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"CONSTRAINT_CATALOG\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"CONSTRAINT_SCHEMA\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"CONSTRAINT_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"UNIQUE_CONSTRAINT_CATALOG\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"UNIQUE_CONSTRAINT_SCHEMA\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"UNIQUE_CONSTRAINT_NAME\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"MATCH_OPTION\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"UPDATE_RULE\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"DELETE_RULE\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    &ast.Ident{Name: \"SPANNER_STATE\"},\n\t\t\t\tType:    &ast.SizedSchemaType{Name: ast.StringTypeName, Max: true},\n\t\t\t\tNotNull: true,\n\t\t\t},\n\t\t},\n\t\tPrimaryKeys: []*ast.IndexKey{\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"CONSTRAINT_CATALOG\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"CONSTRAINT_SCHEMA\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: &ast.Ident{Name: \"CONSTRAINT_NAME\"},\n\t\t\t\tDir:  ast.DirectionAsc,\n\t\t\t},\n\t\t},\n\t},\n}\n"
  },
  {
    "path": "server/query.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/cloudspannerecosystem/memefish/ast\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nvar defaultTimeZone = \"America/Los_Angeles\"\nvar parseLocation *time.Location\n\nfunc init() {\n\tloc, err := time.LoadLocation(defaultTimeZone)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tparseLocation = loc\n}\n\ntype QueryBuilder struct {\n\tdb   *database\n\ttx   *transaction\n\tstmt ast.QueryExpr\n\tdml  ast.DML\n\n\tviews    map[string]*TableView\n\trootView *TableView\n\tparams   map[string]Value\n\n\targs []interface{}\n\n\tunnestViewNum   int\n\tsubqueryViewNum int\n\n\t// forceColumnAlias is used only for array subquery.\n\t// array subquery requires column name for the result item.\n\t// When forceColumnAlias is specified, random column names is used if the item does not have name.\n\tforceColumnAlias bool\n}\n\nfunc BuildQuery(db *database, tx *transaction, stmt ast.QueryExpr, params map[string]Value, forceColumnAlias bool) (string, []interface{}, []ResultItem, error) {\n\tb := &QueryBuilder{\n\t\tdb:               db,\n\t\ttx:               tx,\n\t\tstmt:             stmt,\n\t\tviews:            make(map[string]*TableView),\n\t\tparams:           params,\n\t\tforceColumnAlias: forceColumnAlias,\n\t}\n\treturn b.Build()\n}\n\nfunc BuildDML(db *database, tx *transaction, dml ast.DML, params map[string]Value) (string, []interface{}, error) {\n\tb := &QueryBuilder{\n\t\tdb:     db,\n\t\ttx:     tx,\n\t\tdml:    dml,\n\t\tviews:  make(map[string]*TableView),\n\t\tparams: params,\n\t}\n\treturn b.BuildDML()\n}\n\nfunc (b *QueryBuilder) Build() (string, []interface{}, []ResultItem, error) {\n\tswitch q := b.stmt.(type) {\n\tcase *ast.Query:\n\t\t// ast.Query is a wrapper that contains OrderBy/Limit/WITH/PipeOperators\n\t\t// Delegate to the inner query and handle the additional clauses\n\t\treturn b.buildQueryWithClauses(q)\n\tcase *ast.Select:\n\t\treturn b.buildSelectQuery(q)\n\tcase *ast.SubQuery:\n\t\treturn b.buildSubQuery(q)\n\tcase *ast.CompoundQuery:\n\t\treturn b.buildCompoundQuery(q)\n\t}\n\n\treturn \"\", nil, nil, status.Errorf(codes.Unimplemented, \"unsupported query expression %T\", b.stmt)\n}\n\nfunc (b *QueryBuilder) buildQueryWithClauses(query *ast.Query) (string, []interface{}, []ResultItem, error) {\n\t// First, build the inner query\n\tvar baseQuery string\n\tvar baseData []interface{}\n\tvar resultItems []ResultItem\n\tvar err error\n\n\tswitch innerQuery := query.Query.(type) {\n\tcase *ast.Select:\n\t\tbaseQuery, baseData, resultItems, err = b.buildSelectQuery(innerQuery)\n\tcase *ast.SubQuery:\n\t\tbaseQuery, baseData, resultItems, err = b.buildSubQuery(innerQuery)\n\tcase *ast.CompoundQuery:\n\t\tbaseQuery, baseData, resultItems, err = b.buildCompoundQuery(innerQuery)\n\tcase *ast.Query:\n\t\t// Nested ast.Query (recursive case)\n\t\tbaseQuery, baseData, resultItems, err = b.buildQueryWithClauses(innerQuery)\n\tdefault:\n\t\treturn \"\", nil, nil, status.Errorf(codes.Unimplemented, \"unsupported inner query type %T in ast.Query\", innerQuery)\n\t}\n\n\tif err != nil {\n\t\treturn \"\", nil, nil, err\n\t}\n\n\t// Add the collected data\n\tb.args = append(b.args, baseData...)\n\n\t// Build additional clauses from ast.Query\n\tvar orderByClause string\n\tif query.OrderBy != nil {\n\t\tview := createTableViewFromItems(resultItems, nil)\n\t\ts, data, err := b.buildQueryOrderByClause(query.OrderBy, view)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, nil, err\n\t\t}\n\t\torderByClause = fmt.Sprintf(\" ORDER BY %s\", s)\n\t\tb.args = append(b.args, data...)\n\t}\n\n\tvar limitClause string\n\tif query.Limit != nil {\n\t\ts, data, err := b.buildQueryLimitOffset(query.Limit)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, nil, err\n\t\t}\n\t\tlimitClause = fmt.Sprintf(\" LIMIT %s\", s)\n\t\tb.args = append(b.args, data...)\n\t}\n\n\t// TODO: Handle WITH clause and PipeOperators if needed\n\t// if query.With != nil { ... }\n\t// if len(query.PipeOperators) > 0 { ... }\n\n\tfinalQuery := fmt.Sprintf(\"%s%s%s\", baseQuery, orderByClause, limitClause)\n\treturn finalQuery, b.args, resultItems, nil\n}\n\nfunc (b *QueryBuilder) BuildDML() (string, []interface{}, error) {\n\tswitch q := b.dml.(type) {\n\tcase *ast.Insert:\n\t\treturn b.buildInsert(q)\n\tcase *ast.Delete:\n\t\treturn b.buildDelete(q)\n\tcase *ast.Update:\n\t\treturn b.buildUpdate(q)\n\t}\n\n\treturn \"\", nil, status.Errorf(codes.Unimplemented, \"not unknown expression %T\", b.stmt)\n}\n\nfunc (b *QueryBuilder) registerTableAlias(view *TableView, name string) error {\n\tif _, ok := b.views[name]; ok {\n\t\treturn newExprErrorf(nil, true, \"Duplicate table alias a in the same FROM clause\")\n\t}\n\tb.views[name] = view\n\treturn nil\n}\n\nfunc (b *QueryBuilder) buildSelectQuery(selectStmt *ast.Select) (string, []interface{}, []ResultItem, error) {\n\tif len(selectStmt.Results) == 0 {\n\t\treturn \"\", nil, nil, status.Errorf(codes.InvalidArgument, \"Invalid query\")\n\t}\n\n\tvar fromClause string\n\tvar fromData []interface{}\n\tif selectStmt.From != nil {\n\t\tview, s, d, err := b.buildQueryTable(selectStmt.From.Source)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, nil, err\n\t\t}\n\t\tfromClause = \"FROM \" + s\n\t\tfromData = d\n\t\tb.rootView = view\n\t}\n\n\tresultItems, selectQuery, err := b.buildResultSet(selectStmt.Results)\n\tif err != nil {\n\t\treturn \"\", nil, nil, err\n\t}\n\n\tb.args = append(b.args, fromData...)\n\n\twhereClause, err := b.buildQuery(selectStmt)\n\tif err != nil {\n\t\treturn \"\", nil, nil, err\n\t}\n\n\tif selectStmt.AllOrDistinct == ast.AllOrDistinctDistinct {\n\t\tselectQuery = \"DISTINCT \" + selectQuery\n\t}\n\n\tquery := fmt.Sprintf(`SELECT %s %s %s`, selectQuery, fromClause, whereClause)\n\n\tvar originalNames []string\n\tif b.forceColumnAlias || (selectStmt.As != nil && isAsStruct(selectStmt.As)) {\n\t\tnewResultItems := make([]ResultItem, len(resultItems))\n\t\tnames := make([]string, len(resultItems))\n\t\toriginalNames = make([]string, len(resultItems))\n\t\tfor i := range resultItems {\n\t\t\tname := fmt.Sprintf(\"___column%d\", i)\n\t\t\tnames[i] = name\n\t\t\toriginalNames[i] = resultItems[i].Name\n\t\t\tnewResultItems[i] = ResultItem{\n\t\t\t\tName:      name,\n\t\t\t\tValueType: resultItems[i].ValueType,\n\t\t\t\tExpr: Expr{\n\t\t\t\t\tRaw:       name,\n\t\t\t\t\tValueType: resultItems[i].ValueType,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\t\tquery = fmt.Sprintf(`WITH ___CTE(%s) as (%s) select * from ___CTE`, strings.Join(names, \", \"), query)\n\t\tresultItems = newResultItems\n\t}\n\n\tif selectStmt.As != nil && isAsStruct(selectStmt.As) {\n\t\tuseSqliteJSON()\n\t\tvalues := make([]string, len(resultItems))\n\t\tquotedNames := make([]string, len(resultItems))\n\t\tvts := make([]*ValueType, len(resultItems))\n\t\tfor i := range resultItems {\n\t\t\tif resultItems[i].ValueType.Code == TCArray {\n\t\t\t\t// column with JSON type needs converting to JSON explictly when reading from table\n\t\t\t\t// otherwise it is parsed as string\n\t\t\t\t// TODO: do this in referring timing\n\t\t\t\tvalues[i] = fmt.Sprintf(\"JSON(%s)\", resultItems[i].Name)\n\t\t\t} else {\n\t\t\t\tvalues[i] = resultItems[i].Name\n\t\t\t}\n\t\t\tquotedNames[i] = fmt.Sprintf(\"'%s'\", originalNames[i])\n\t\t\tvts[i] = &resultItems[i].ValueType\n\t\t}\n\n\t\tvt := ValueType{\n\t\t\tCode: TCStruct,\n\t\t\tStructType: &StructType{\n\t\t\t\tFieldNames: originalNames,\n\t\t\t\tFieldTypes: vts,\n\t\t\t},\n\t\t}\n\n\t\tresult := ResultItem{\n\t\t\tName:      \"\",\n\t\t\tValueType: vt,\n\t\t\tExpr: Expr{\n\t\t\t\tRaw:       \"___AsStruct\",\n\t\t\t\tValueType: vt,\n\t\t\t},\n\t\t}\n\n\t\tnamesObj := fmt.Sprintf(\"JSON_ARRAY(%s)\", strings.Join(quotedNames, \", \"))\n\t\tvaluesObj := fmt.Sprintf(\"JSON_ARRAY(%s)\", strings.Join(values, \", \"))\n\t\tquery = fmt.Sprintf(`SELECT JSON_OBJECT(\"keys\", %s, \"values\", %s) AS ___AsStruct FROM (%s)`, namesObj, valuesObj, query)\n\t\tresultItems = []ResultItem{result}\n\t}\n\n\treturn query, b.args, resultItems, nil\n}\n\nfunc (b *QueryBuilder) buildSubQuery(sub *ast.SubQuery) (string, []interface{}, []ResultItem, error) {\n\ts, data, items, err := BuildQuery(b.db, b.tx, sub.Query, b.params, b.forceColumnAlias)\n\tif err != nil {\n\t\treturn \"\", nil, nil, err\n\t}\n\n\t// Note: OrderBy/Limit are not part of ast.SubQuery\n\t// They are only available in ast.Query wrapper. If this SubQuery\n\t// has OrderBy/Limit, it would be wrapped in ast.Query and handled there.\n\n\treturn fmt.Sprintf(\"(%s)\", s), data, items, nil\n}\n\nfunc (b *QueryBuilder) buildCompoundQuery(compound *ast.CompoundQuery) (string, []interface{}, []ResultItem, error) {\n\tvar ss []string\n\n\tvar op string\n\tvar fullOpName string\n\tswitch compound.Op {\n\tcase ast.SetOpUnion:\n\t\tif compound.AllOrDistinct == ast.AllOrDistinctDistinct {\n\t\t\t// distinct is used by default in sqlite\n\t\t\top = \"UNION\"\n\t\t\tfullOpName = \"UNION DISTINCT\"\n\t\t} else {\n\t\t\top = \"UNION ALL\"\n\t\t\tfullOpName = \"UNION ALL\"\n\t\t}\n\tcase ast.SetOpIntersect:\n\t\tif compound.AllOrDistinct == ast.AllOrDistinctDistinct {\n\t\t\t// distinct is used by default in sqlite\n\t\t\top = \"INTERSECT\"\n\t\t\tfullOpName = \"INTERSECT DISTINCT\"\n\t\t} else {\n\t\t\t// sqlite does not support INTERSECT ALL\n\t\t\t// TODO: simulation of INTERSECT ALL\n\t\t\treturn \"\", nil, nil, status.Errorf(codes.Unimplemented, \"INTERSECT ALL is not supported yet\")\n\t\t}\n\tcase ast.SetOpExcept:\n\t\tif compound.AllOrDistinct == ast.AllOrDistinctDistinct {\n\t\t\t// distinct is used by default in sqlite\n\t\t\top = \"EXCEPT\"\n\t\t\tfullOpName = \"EXCEPT DISTINCT\"\n\t\t} else {\n\t\t\t// sqlite does not support EXCEPT ALL\n\t\t\t// TODO: simulation of EXCEPT ALL\n\t\t\treturn \"\", nil, nil, status.Errorf(codes.Unimplemented, \"EXCEPT ALL is not supported yet\")\n\t\t}\n\t}\n\n\ts, data, items, err := BuildQuery(b.db, b.tx, compound.Queries[0], b.params, b.forceColumnAlias)\n\tif err != nil {\n\t\treturn \"\", nil, nil, err\n\t}\n\n\t// sqlite does not allow to put parentheses around select statement like:\n\t// (SELECT .. FROM xxx) UNION ALL (SELECT ... FROM yyy)\n\t//\n\t// This causes ambiguous LIMIT clause like:\n\t// SELECT .. FROM xxx UNION ALL SELECT ... FROM yyy LIMIT 2\n\t//\n\t// This is possibly interpreted as\n\t// SELECT .. FROM xxx UNION ALL (SELECT ... FROM yyy LIMIT 2)\n\t// SELECT .. FROM xxx UNION ALL (SELECT ... FROM yyy) LIMIT 2\n\t//\n\t// So use subquery to fix the issue like this:\n\t// SELECT * FROM (SELECT .. FROM xxx) UNION ALL SELECT * FROM (SELECT ... FROM yyy)\n\tss = append(ss, fmt.Sprintf(\"SELECT * FROM (%s)\", s))\n\n\tfor i, query := range compound.Queries[1:] {\n\t\ts, d, items2, err := BuildQuery(b.db, b.tx, query, b.params, false)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, nil, err\n\t\t}\n\n\t\tif len(items) != len(items2) {\n\t\t\treturn \"\", nil, nil, status.Errorf(codes.InvalidArgument,\n\t\t\t\t\"Queries in %s have mismatched column count; query 1 has %d column, query %d has %d columns\",\n\t\t\t\tfullOpName, len(items), i+2, len(items2))\n\t\t}\n\n\t\t// check the result items match to the first query's items\n\t\tfor j := range items {\n\t\t\tif !compareValueType(items[j].ValueType, items2[j].ValueType) {\n\t\t\t\treturn \"\", nil, nil, status.Errorf(codes.InvalidArgument,\n\t\t\t\t\t\"Column %d in %s has incompatible types: %s, %s\",\n\t\t\t\t\tj+1, fullOpName, items[j].ValueType, items2[j].ValueType)\n\t\t\t}\n\t\t}\n\n\t\tss = append(ss, fmt.Sprintf(\"SELECT * FROM (%s)\", s))\n\t\tdata = append(data, d...)\n\t}\n\n\t// Note: OrderBy/Limit are not part of ast.CompoundQuery\n\t// They are only available in ast.Query wrapper. If this CompoundQuery\n\t// has OrderBy/Limit, it would be wrapped in ast.Query and handled there.\n\n\tquery := strings.Join(ss, fmt.Sprintf(\" %s \", op))\n\treturn query, data, items, nil\n}\n\nfunc (b *QueryBuilder) buildUpdate(up *ast.Update) (string, []interface{}, error) {\n\tt, err := b.db.useTableExclusive(up.TableName.Idents[0].Name, b.tx)\n\tif err != nil {\n\t\tif status.Code(err) == codes.NotFound {\n\t\t\treturn \"\", nil, status.Error(codes.InvalidArgument, err.Error())\n\t\t}\n\t\treturn \"\", nil, err\n\t}\n\tview := t.TableView()\n\tb.rootView = view\n\n\t// build FROM\n\tvar fromClause string\n\tvar tableAliiasName string\n\tif up.As == nil {\n\t\tfromClause = QuoteString(t.Name)\n\t\ttableAliiasName = t.Name\n\t\tif err := b.registerTableAlias(view, t.Name); err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\t} else {\n\t\tfromClause = fmt.Sprintf(\"%s AS %s\", QuoteString(t.Name), QuoteString(up.As.Alias.Name))\n\t\ttableAliiasName = up.As.Alias.Name\n\t\tif err := b.registerTableAlias(view, up.As.Alias.Name); err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\t}\n\n\t// build SET\n\tvar setClause []string\n\tusedPaths := make(map[string]struct{}, len(up.Updates))\n\tfor _, item := range up.Updates {\n\t\tvar fullPathName string\n\t\tvar normalizedFullPathName string\n\n\t\tvar path Expr\n\t\tif len(item.Path) == 1 {\n\t\t\tex, err := b.buildExpr(item.Path[0])\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", nil, wrapExprError(err, item.DefaultExpr.Expr, \"Updates\")\n\t\t\t}\n\t\t\tpath = ex\n\t\t\tfullPathName = item.Path[0].Name\n\t\t\tnormalizedFullPathName = item.Path[0].Name\n\t\t} else {\n\t\t\tex, err := b.buildExpr(&ast.Path{Idents: item.Path})\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", nil, wrapExprError(err, item.DefaultExpr.Expr, \"Updates\")\n\t\t\t}\n\t\t\tpath = ex\n\n\t\t\t// generate full path name\n\t\t\tpaths := make([]string, len(item.Path))\n\t\t\tfor i := range item.Path {\n\t\t\t\tpaths[i] = item.Path[i].Name\n\t\t\t}\n\t\t\tfullPathName = strings.Join(paths, \".\")\n\n\t\t\t// if the first path is same to table alias name, normalize the full path name\n\t\t\tif paths[0] == tableAliiasName {\n\t\t\t\tnormalizedFullPathName = strings.Join(paths[1:], \".\")\n\t\t\t} else {\n\t\t\t\tnormalizedFullPathName = fullPathName\n\t\t\t}\n\t\t}\n\n\t\t// check duplicate path in set clause\n\t\tif _, ok := usedPaths[normalizedFullPathName]; ok {\n\t\t\treturn \"\", nil, status.Errorf(codes.InvalidArgument, \"Update item %s assigned more than once\", fullPathName)\n\t\t}\n\t\tusedPaths[normalizedFullPathName] = struct{}{}\n\n\t\tvalue, err := b.buildExpr(item.DefaultExpr.Expr)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, wrapExprError(err, item.DefaultExpr.Expr, \"Updates\")\n\t\t}\n\t\tb.args = append(b.args, value.Args...)\n\n\t\tsetClause = append(setClause, fmt.Sprintf(\"%s = %s\", path.Raw, value.Raw))\n\t}\n\n\t// build WHERE\n\twhereClause, data, err := b.buildQueryWhereClause(up.Where)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tb.args = append(b.args, data...)\n\n\tquery := fmt.Sprintf(`UPDATE %s SET %s WHERE %s`, fromClause, strings.Join(setClause, \", \"), whereClause)\n\n\treturn query, b.args, nil\n}\n\nfunc (b *QueryBuilder) buildInsert(up *ast.Insert) (string, []interface{}, error) {\n\tt, err := b.db.useTableExclusive(up.TableName.Idents[0].Name, b.tx)\n\tif err != nil {\n\t\tif status.Code(err) == codes.NotFound {\n\t\t\treturn \"\", nil, status.Error(codes.InvalidArgument, err.Error())\n\t\t}\n\t\treturn \"\", nil, err\n\t}\n\tview := t.TableView()\n\tb.rootView = view\n\ttableName := t.Name\n\n\tvar columns []string\n\tfor _, c := range up.Columns {\n\t\tcolumns = append(columns, c.Name)\n\t}\n\n\t// Check columns exist in the table\n\tfor _, c := range columns {\n\t\tif _, err := t.getColumn(c); err != nil {\n\t\t\treturn \"\", nil, status.Errorf(codes.InvalidArgument, \"Column %s is not present in table %s\", c, tableName)\n\t\t}\n\t}\n\n\t// Ensure multiple values are not specified\n\tusedColumns := make(map[string]struct{}, len(columns))\n\tfor _, name := range columns {\n\t\tif _, ok := usedColumns[name]; ok {\n\t\t\treturn \"\", nil, status.Errorf(codes.InvalidArgument, \"INSERT has columns with duplicate name: %s\", name)\n\t\t}\n\t\tusedColumns[name] = struct{}{}\n\t}\n\n\t// Check not nullable columns\n\tif exist, nonNullables := t.NonNullableAndNonGeneratedColumnsExist(columns); exist {\n\t\tcolumns := strings.Join(nonNullables, \", \")\n\t\treturn \"\", nil, status.Errorf(codes.FailedPrecondition,\n\t\t\t\"A new row in table %s does not specify a non-null value for these NOT NULL columns: %s\",\n\t\t\ttableName, columns,\n\t\t)\n\t}\n\n\tvar values string\n\tswitch input := up.Input.(type) {\n\tcase *ast.ValuesInput:\n\t\tvar rows []string\n\t\tfor _, row := range input.Rows {\n\t\t\tif len(row.Exprs) != len(columns) {\n\t\t\t\treturn \"\", nil, status.Errorf(codes.InvalidArgument,\n\t\t\t\t\t\"Inserted row has wrong column count; Has %d, expected %d\",\n\t\t\t\t\tlen(row.Exprs), len(columns),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tvar values []string\n\t\t\tfor _, e := range row.Exprs {\n\t\t\t\tif e.Default {\n\t\t\t\t\tvalues = append(values, \"NULL\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tex, err := b.buildExpr(e.Expr)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", nil, wrapExprError(err, e.Expr, \"INSERT\")\n\t\t\t\t}\n\t\t\t\tb.args = append(b.args, ex.Args...)\n\t\t\t\tvalues = append(values, ex.Raw)\n\t\t\t}\n\n\t\t\trows = append(rows, fmt.Sprintf(\"(%s)\", strings.Join(values, \", \")))\n\t\t}\n\n\t\tvalues = fmt.Sprintf(\"VALUES %s\", strings.Join(rows, \", \"))\n\tcase *ast.SubQueryInput:\n\t\tquery, data, items, err := BuildQuery(b.db, b.tx, input.Query, b.params, false)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, fmt.Errorf(\"Subquery error: %v\", err)\n\t\t}\n\t\tb.args = append(b.args, data...)\n\n\t\tif len(items) != len(columns) {\n\t\t\treturn \"\", nil, status.Errorf(codes.InvalidArgument,\n\t\t\t\t\"Inserted row has wrong column count; Has %d, expected %d\",\n\t\t\t\tlen(items), len(columns),\n\t\t\t)\n\t\t}\n\n\t\tvalues = query\n\t}\n\n\tquery := fmt.Sprintf(`INSERT INTO %s (%s) %s`, QuoteString(tableName), strings.Join(QuoteStringSlice(columns), \", \"), values)\n\n\treturn query, b.args, nil\n}\n\nfunc (b *QueryBuilder) buildDelete(up *ast.Delete) (string, []interface{}, error) {\n\tt, err := b.db.useTableExclusive(up.TableName.Idents[0].Name, b.tx)\n\tif err != nil {\n\t\tif status.Code(err) == codes.NotFound {\n\t\t\treturn \"\", nil, status.Error(codes.InvalidArgument, err.Error())\n\t\t}\n\t\treturn \"\", nil, err\n\t}\n\tview := t.TableView()\n\tb.rootView = view\n\n\t// build FROM\n\tvar fromClause string\n\tif up.As == nil {\n\t\tfromClause = QuoteString(t.Name)\n\t\tif err := b.registerTableAlias(view, t.Name); err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\t} else {\n\t\tfromClause = fmt.Sprintf(\"%s AS %s\", QuoteString(t.Name), QuoteString(up.As.Alias.Name))\n\t\tif err := b.registerTableAlias(view, up.As.Alias.Name); err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\t}\n\n\t// build WHERE\n\twhereClause, data, err := b.buildQueryWhereClause(up.Where)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tb.args = append(b.args, data...)\n\n\tquery := fmt.Sprintf(`DELETE FROM %s WHERE %s`, fromClause, whereClause)\n\n\treturn query, b.args, nil\n}\n\nfunc (b *QueryBuilder) buildQueryTable(exp ast.TableExpr) (*TableView, string, []interface{}, error) {\n\tswitch src := exp.(type) {\n\tcase *ast.TableName:\n\t\tt, err := b.db.useTable(src.Table.Name, b.tx)\n\t\tif err != nil {\n\t\t\tif status.Code(err) == codes.NotFound {\n\t\t\t\treturn nil, \"\", nil, status.Error(codes.InvalidArgument, err.Error())\n\t\t\t}\n\t\t\treturn nil, \"\", nil, err\n\t\t}\n\n\t\tquery := QuoteString(t.Name)\n\t\talias := t.Name\n\t\tif src.As != nil {\n\t\t\talias = src.As.Alias.Name\n\t\t\tquery = fmt.Sprintf(\"%s AS %s\", QuoteString(t.Name), QuoteString(alias))\n\t\t} else if schema, ok := metaTablesReverseMap[t.Name]; ok {\n\t\t\talias = schema[1]\n\t\t\tquery = fmt.Sprintf(\"%s AS %s\", QuoteString(t.Name), QuoteString(alias))\n\t\t}\n\n\t\tview := t.TableViewWithAlias(alias)\n\n\t\tif err := b.registerTableAlias(view, alias); err != nil {\n\t\t\treturn nil, \"\", nil, err\n\t\t}\n\n\t\treturn view, query, nil, nil\n\n\tcase *ast.PathTableExpr:\n\t\t// PathTableExpr is used for tables that might be in a schema or implicit UNNEST\n\t\tif len(src.Path.Idents) == 0 {\n\t\t\treturn nil, \"\", nil, status.Error(codes.InvalidArgument, \"Empty path in table expression\")\n\t\t}\n\n\t\t// Build the full path string to check for INFORMATION_SCHEMA tables\n\t\tpathNames := make([]string, len(src.Path.Idents))\n\t\tfor i, ident := range src.Path.Idents {\n\t\t\tpathNames[i] = ident.Name\n\t\t}\n\t\tfullPath := strings.Join(pathNames, \".\")\n\n\t\t// Check if this is a meta table (like INFORMATION_SCHEMA.SCHEMATA)\n\t\tvar tableName string\n\t\tif specialTableName, ok := metaTablesMap[fullPath]; ok {\n\t\t\ttableName = specialTableName\n\t\t} else {\n\t\t\t// Fall back to using the last identifier as table name\n\t\t\ttableName = src.Path.Idents[len(src.Path.Idents)-1].Name\n\t\t}\n\n\t\tt, err := b.db.useTable(tableName, b.tx)\n\t\tif err != nil {\n\t\t\tif status.Code(err) == codes.NotFound {\n\t\t\t\treturn nil, \"\", nil, status.Error(codes.InvalidArgument, err.Error())\n\t\t\t}\n\t\t\treturn nil, \"\", nil, err\n\t\t}\n\n\t\tquery := QuoteString(t.Name)\n\t\talias := t.Name\n\t\tif src.As != nil {\n\t\t\talias = src.As.Alias.Name\n\t\t\tquery = fmt.Sprintf(\"%s AS %s\", QuoteString(t.Name), QuoteString(alias))\n\t\t} else if schema, ok := metaTablesReverseMap[t.Name]; ok {\n\t\t\talias = schema[1]\n\t\t\tquery = fmt.Sprintf(\"%s AS %s\", QuoteString(t.Name), QuoteString(alias))\n\t\t}\n\n\t\tview := t.TableViewWithAlias(alias)\n\n\t\tif err := b.registerTableAlias(view, alias); err != nil {\n\t\t\treturn nil, \"\", nil, err\n\t\t}\n\n\t\treturn view, query, nil, nil\n\tcase *ast.Join:\n\t\treturn b.buildJoinedView(src)\n\n\tcase *ast.Unnest:\n\t\t// TODO: fix this hack\n\t\t// memefish handles  _SCHEMA_._TABLE_ table name as unnest.\n\t\t// So if the unnest path is the same to _SCHEMA_._TABLE_ it is handled as table name.\n\t\tif path, ok := src.Expr.(*ast.Path); ok {\n\t\t\tnames := make([]string, len(path.Idents))\n\t\t\tfor i := range path.Idents {\n\t\t\t\tnames[i] = path.Idents[i].Name\n\t\t\t}\n\t\t\ttableName := strings.ToUpper(strings.Join(names, \".\"))\n\t\t\tif specialTableName, ok := metaTablesMap[tableName]; ok {\n\t\t\t\treturn b.buildQueryTable(&ast.TableName{\n\t\t\t\t\tTable: &ast.Ident{Name: specialTableName},\n\t\t\t\t\tAs:    src.As,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\treturn b.buildUnnestView(src)\n\n\tcase *ast.SubQueryTableExpr:\n\t\tquery, data, items, err := BuildQuery(b.db, b.tx, src.Query, b.params, false)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", nil, fmt.Errorf(\"Subquery error: %v\", err)\n\t\t}\n\n\t\tview := createTableViewFromItems(items, nil)\n\n\t\tvar viewName string\n\t\tif src.As == nil {\n\t\t\tviewName = fmt.Sprintf(\"__SUBQUERY%d\", b.subqueryViewNum)\n\t\t\tb.subqueryViewNum++\n\t\t} else {\n\t\t\tviewName = src.As.Alias.Name\n\t\t\tif err := b.registerTableAlias(view, viewName); err != nil {\n\t\t\t\treturn nil, \"\", nil, err\n\t\t\t}\n\t\t}\n\n\t\treturn view, fmt.Sprintf(\"(%s) AS %s\", query, viewName), data, nil\n\tcase *ast.ParenTableExpr:\n\t\tview, q, d, err := b.buildQueryTable(src.Source)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", nil, err\n\t\t}\n\t\treturn view, fmt.Sprintf(\"(%s)\", q), d, nil\n\tdefault:\n\t\treturn nil, \"\", nil, status.Errorf(codes.Unknown, \"unknown expression %T for FROM\", src)\n\t}\n}\n\nfunc (b *QueryBuilder) buildJoinedView(src *ast.Join) (*TableView, string, []interface{}, error) {\n\tleftView, leftQuery, leftData, err := b.buildQueryTable(src.Left)\n\tif err != nil {\n\t\treturn nil, \"\", nil, fmt.Errorf(\"left table error %v\", err)\n\t}\n\tif leftView == nil {\n\t\treturn nil, \"\", nil, fmt.Errorf(\"left table view is nil\")\n\t}\n\n\trightView, rightQuery, rightData, err := b.buildQueryTable(src.Right)\n\tif err != nil {\n\t\treturn nil, \"\", nil, fmt.Errorf(\"right table error %v\", err)\n\t}\n\tif rightView == nil {\n\t\treturn nil, \"\", nil, fmt.Errorf(\"right table view is nil\")\n\t}\n\n\tleftItems := leftView.AllItems()\n\trightItems := rightView.AllItems()\n\n\tvar condData []interface{}\n\tvar condition string\n\tvar usingColumns []string\n\tswitch cond := src.Cond.(type) {\n\tcase *ast.On:\n\t\tswitch expr := cond.Expr.(type) {\n\t\tcase *ast.BinaryExpr:\n\t\t\tex, err := b.buildExpr(expr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, \"\", nil, wrapExprError(err, expr, \"ON\")\n\t\t\t}\n\t\t\tcondition = fmt.Sprintf(\"ON %s\", ex.Raw)\n\t\t\tcondData = append(condData, ex.Args...)\n\n\t\tdefault:\n\t\t\treturn nil, \"\", nil, status.Errorf(codes.Unimplemented, \"not supported expression %T for JOIN condition\", expr)\n\t\t}\n\n\tcase *ast.Using:\n\t\tnames := make([]string, len(cond.Idents))\n\t\tfor i := range cond.Idents {\n\t\t\tname := cond.Idents[i].Name\n\t\t\tif _, ok := leftView.ResultItemsMap[name]; !ok {\n\t\t\t\treturn nil, \"\", nil, newExprErrorf(nil, true, \"Column %s in USING clause not found on left side of join\", name)\n\t\t\t}\n\t\t\tif _, ok := rightView.ResultItemsMap[name]; !ok {\n\t\t\t\treturn nil, \"\", nil, newExprErrorf(nil, true, \"Column %s in USING clause not found on right side of join\", name)\n\t\t\t}\n\t\t\tnames[i] = name\n\t\t}\n\n\t\tusingColumns = names // used for FULL OUTER JOIN\n\t\tcondition = fmt.Sprintf(\"USING (%s)\", strings.Join(QuoteStringSlice(names), \", \"))\n\n\t\t// filter ResultItems that used in USING columns from right view\n\t\tvar newRightItems []ResultItem\n\t\tfor i := range rightItems {\n\t\t\tvar found bool\n\t\t\tfor _, name := range names {\n\t\t\t\tif rightItems[i].Name == name {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !found {\n\t\t\t\tnewRightItems = append(newRightItems, rightItems[i])\n\t\t\t}\n\t\t}\n\t\trightItems = newRightItems\n\t}\n\n\tswitch src.Op {\n\tcase ast.CommaJoin,\n\t\tast.CrossJoin,\n\t\tast.InnerJoin,\n\t\tast.LeftOuterJoin:\n\t\tnewView := createTableViewFromItems(leftItems, rightItems)\n\t\tdata := append(append(leftData, rightData...), condData...)\n\n\t\treturn newView, fmt.Sprintf(\"%s %s %s %s\", leftQuery, src.Op, rightQuery, condition), data, nil\n\n\tcase ast.RightOuterJoin:\n\t\t// RIGHT OUTER JOIN is not supported by sqlite\n\t\t// Reverse left and right, then use LEFT OUTER JOIN to simulate RIGHT OUTER JOIN\n\t\tnewView := createTableViewFromItems(leftItems, rightItems)\n\t\tdata := append(append(rightData, leftData...), condData...)\n\n\t\treturn newView, fmt.Sprintf(\"%s LEFT OUTER JOIN %s %s\", rightQuery, leftQuery, condition), data, nil\n\n\tcase ast.FullOuterJoin:\n\t\t// FULL OUTER JOIN is not supported by sqlite\n\t\t// Use LEFT OUTER JOIN and RIGHT OUTER JOIN, then UNION ALL to simulate FULL OUTER JOIN\n\t\t// Ref. https://www.sqlitetutorial.net/sqlite-full-outer-join/\n\n\t\tif len(usingColumns) == 0 {\n\t\t\tmsg := \"hanndy-spanner: Full Outer Join with ON condition is not supported yet.\"\n\t\t\treturn nil, \"\", nil, status.Errorf(codes.Unknown, msg)\n\t\t}\n\n\t\tvar leftUsingColumns []string\n\t\tvar leftRemainingColumns []string\n\t\tfor _, item := range leftView.AllItems() {\n\t\t\tvar found bool\n\t\t\tfor _, name := range usingColumns {\n\t\t\t\tif item.Name == name {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif found {\n\t\t\t\tleftUsingColumns = append(leftUsingColumns, item.Expr.Raw)\n\t\t\t} else {\n\t\t\t\tleftRemainingColumns = append(leftRemainingColumns, item.Expr.Raw)\n\t\t\t}\n\t\t}\n\n\t\tvar rightUsingColumns []string\n\t\tvar rightRemainingColumns []string\n\t\tfor _, item := range rightView.AllItems() {\n\t\t\tvar found bool\n\t\t\tfor _, name := range usingColumns {\n\t\t\t\tif item.Name == name {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif found {\n\t\t\t\trightUsingColumns = append(rightUsingColumns, item.Expr.Raw)\n\t\t\t} else {\n\t\t\t\trightRemainingColumns = append(rightRemainingColumns, item.Expr.Raw)\n\t\t\t}\n\t\t}\n\t\tremainingColumns := append(leftRemainingColumns, rightRemainingColumns...)\n\n\t\tvar secondWhere []string\n\t\tfor _, c := range leftUsingColumns {\n\t\t\tsecondWhere = append(secondWhere, fmt.Sprintf(\"%s IS NULL\", c))\n\t\t}\n\n\t\tfirst := fmt.Sprintf(`SELECT %s FROM %s LEFT JOIN %s %s`,\n\t\t\tstrings.Join(append(leftUsingColumns, remainingColumns...), \", \"),\n\t\t\tleftQuery, rightQuery, condition)\n\t\tsecond := fmt.Sprintf(`SELECT %s FROM %s LEFT JOIN %s %s WHERE %s`,\n\t\t\tstrings.Join(append(rightUsingColumns, remainingColumns...), \", \"),\n\t\t\trightQuery, leftQuery, condition,\n\t\t\tstrings.Join(secondWhere, \" AND \"),\n\t\t)\n\t\tquery := fmt.Sprintf(\"(%s UNION ALL %s)\", first, second)\n\n\t\tnewView := createTableViewFromItems(leftItems, rightItems)\n\n\t\tdata1 := append(append(leftData, rightData...), condData...)\n\t\tdata2 := append(append(rightData, leftData...), condData...)\n\t\tdata := append(data1, data2...)\n\n\t\t// TODO: FullOuterJoin is simulated by using subquery with UNION, so table alias cannot be used\n\t\t// See the test case\n\n\t\treturn newView, query, data, nil\n\t}\n\n\treturn nil, \"\", nil, status.Errorf(codes.Unknown, \"unknown Join operation: %v\", src.Op)\n}\n\n// buildUnnestView creates a Tableview from an UNNEST() expression.\n//\n// buildUnnestExpr creates a table like `VALUES (a), (b), (c)`, but the columns are unnamed.\n// If aliases for unnested values or offsets are used, they can be specified by the name.\n// e.g. SELECT MAX(x), MAX(y) FROM UNNEST() AS x OFFSET WITH AS y\n//\n// See the doc comment of buildUnnestExpr about the generated query.\nfunc (b *QueryBuilder) buildUnnestView(src *ast.Unnest) (*TableView, string, []interface{}, error) {\n\tuseSqliteJSON()\n\tvar offset bool\n\tvar offsetAlias string\n\tif src.WithOffset != nil {\n\t\toffset = true\n\t\tif src.WithOffset.As != nil {\n\t\t\toffsetAlias = src.WithOffset.As.Alias.Name\n\t\t}\n\t}\n\n\ts, err := b.buildUnnestExpr(src.Expr, offset, true)\n\tif err != nil {\n\t\treturn nil, \"\", nil, wrapExprError(err, src.Expr, \"UNNEST\")\n\t}\n\n\tviewName := fmt.Sprintf(\"__UNNEST%d\", b.unnestViewNum)\n\tb.unnestViewNum++\n\n\t// If alias is specified, it names unnested column\n\titemName := \"\"\n\tif src.As != nil {\n\t\titemName = src.As.Alias.Name\n\t}\n\n\titems := []ResultItem{\n\t\t{\n\t\t\tName:      itemName,\n\t\t\tValueType: *s.ValueType.ArrayType,\n\t\t\tExpr: Expr{\n\t\t\t\tRaw:       fmt.Sprintf(\"%s.value\", viewName), // implicitly named column1\n\t\t\t\tValueType: *s.ValueType.ArrayType,\n\t\t\t},\n\t\t},\n\t}\n\n\tif offset {\n\t\titems = append(items, ResultItem{\n\t\t\tName:      offsetAlias,\n\t\t\tValueType: ValueType{Code: TCInt64},\n\t\t\tExpr: Expr{\n\t\t\t\tRaw:       fmt.Sprintf(\"%s.offset\", viewName),\n\t\t\t\tValueType: ValueType{Code: TCInt64},\n\t\t\t},\n\t\t})\n\t}\n\n\tview := createTableViewFromItems(items, nil)\n\traw := fmt.Sprintf(\"(%s) AS %s\", s.Raw, viewName)\n\n\t// if the element type is struct, it needs to be expanded\n\t// otherwise return as is\n\tif s.ValueType.ArrayType.Code != TCStruct {\n\t\treturn view, raw, s.Args, nil\n\t}\n\n\tviewItems := view.AllItems()\n\tst := viewItems[0]\n\n\tstrItems := st.ValueType.StructType.AllItems()\n\tvar newItems []ResultItem\n\tfor i := range strItems {\n\t\tnewItems = append(newItems, ResultItem{\n\t\t\tName:      strItems[i].Name,\n\t\t\tValueType: strItems[i].ValueType,\n\t\t\tExpr: Expr{\n\t\t\t\tRaw:       strItems[i].Expr.Raw,\n\t\t\t\tValueType: strItems[i].Expr.ValueType,\n\t\t\t\tArgs:      strItems[i].Expr.Args,\n\t\t\t},\n\t\t})\n\t}\n\n\tvar exprs []string\n\tfor i, item := range newItems {\n\t\tif item.Name == \"\" {\n\t\t\texprs = append(exprs, fmt.Sprintf(\"JSON_EXTRACT(%s, '$.values[%d]')\", \"value\", i))\n\t\t} else {\n\t\t\texprs = append(exprs, fmt.Sprintf(\"JSON_EXTRACT(%s, '$.values[%d]') AS %s\", \"value\", i, item.Name))\n\t\t}\n\t}\n\n\tif src.As != nil {\n\t\tviewName := src.As.Alias.Name\n\t\tview2 := createTableViewFromItems(newItems, nil)\n\t\tif err := b.registerTableAlias(view2, viewName); err != nil {\n\t\t\treturn nil, \"\", nil, err\n\t\t}\n\t}\n\n\tvar offsetItem []ResultItem\n\tif offset {\n\t\toffsetItem = []ResultItem{\n\t\t\t{\n\t\t\t\tName:      viewItems[1].Name,\n\t\t\t\tValueType: viewItems[1].ValueType,\n\t\t\t\tExpr: Expr{\n\t\t\t\t\tRaw:       viewItems[1].Name,\n\t\t\t\t\tValueType: viewItems[1].Expr.ValueType,\n\t\t\t\t\tArgs:      viewItems[1].Expr.Args,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tif viewItems[1].Name == \"\" {\n\t\t\texprs = append(exprs, viewItems[1].Expr.Raw)\n\t\t} else {\n\t\t\texprs = append(exprs, fmt.Sprintf(\"%s AS %s\", viewItems[1].Expr.Raw, viewItems[1].Name))\n\t\t}\n\t}\n\n\tview3 := createTableViewFromItems(newItems, offsetItem)\n\n\tselectQuery := strings.Join(exprs, \", \")\n\n\treturn view3, fmt.Sprintf(\"(SELECT %s FROM %s)\", selectQuery, raw), s.Args, nil\n}\n\nfunc (b *QueryBuilder) buildResultSet(selectItems []ast.SelectItem) ([]ResultItem, string, error) {\n\tvar data []interface{}\n\tn := len(selectItems)\n\tif b.rootView != nil {\n\t\tn += len(b.rootView.ResultItems)\n\t}\n\titems := make([]ResultItem, 0, n)\n\texprs := make([]string, 0, len(selectItems))\n\tfor _, item := range selectItems {\n\t\tswitch i := item.(type) {\n\t\tcase *ast.Star:\n\t\t\tif b.rootView == nil {\n\t\t\t\treturn nil, \"\", status.Errorf(codes.InvalidArgument, `SELECT * must have a FROM clause`)\n\t\t\t}\n\t\t\titems = append(items, b.rootView.AllItems()...)\n\t\t\texprs = append(exprs, \"*\")\n\t\tcase *ast.DotStar:\n\t\t\tex, err := b.buildExpr(i.Expr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, \"\", wrapExprError(err, i.Expr, \"DotStar\")\n\t\t\t}\n\t\t\tdata = append(data, ex.Args...)\n\n\t\t\tif ex.ValueType.Code != TCStruct {\n\t\t\t\treturn nil, \"\", status.Errorf(codes.InvalidArgument, \"Dot-star is not supported for type %s\", ex.ValueType)\n\n\t\t\t}\n\t\t\tst := ex.ValueType.StructType\n\n\t\t\titems = append(items, st.AllItems()...)\n\t\t\tif st.IsTable {\n\t\t\t\texprs = append(exprs, fmt.Sprintf(\"%s.*\", ex.Raw))\n\t\t\t} else {\n\t\t\t\tuseSqliteJSON()\n\t\t\t\tn := len(st.FieldTypes)\n\t\t\t\tfor i := 0; i < n; i++ {\n\t\t\t\t\texprs = append(exprs, fmt.Sprintf(\"JSON_EXTRACT(%s, '$.values[%d]')\", ex.Raw, i))\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase *ast.ExprSelectItem:\n\t\t\tvar alias string\n\t\t\tswitch e := i.Expr.(type) {\n\t\t\tcase *ast.Ident:\n\t\t\t\talias = e.Name\n\t\t\tcase *ast.Path:\n\t\t\t\talias = e.Idents[len(e.Idents)-1].Name\n\t\t\t}\n\t\t\tex, err := b.buildExpr(i.Expr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, \"\", wrapExprError(err, i.Expr, \"Path\")\n\t\t\t}\n\n\t\t\tdata = append(data, ex.Args...)\n\t\t\titems = append(items, ResultItem{\n\t\t\t\tName:      alias,\n\t\t\t\tValueType: ex.ValueType,\n\t\t\t\tExpr: Expr{\n\t\t\t\t\t// This is a trick. This Expr is referred as subquery.\n\t\t\t\t\t// At the reference, it should be just referred as alias name instead of s.Raw.\n\t\t\t\t\tRaw:       QuoteString(alias),\n\t\t\t\t\tValueType: ex.ValueType,\n\t\t\t\t},\n\t\t\t})\n\n\t\t\texprs = append(exprs, ex.Raw)\n\n\t\tcase *ast.Alias:\n\t\t\talias := i.As.Alias.Name\n\t\t\tex, err := b.buildExpr(i.Expr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, \"\", wrapExprError(err, i.Expr, \"Alias\")\n\t\t\t}\n\t\t\tdata = append(data, ex.Args...)\n\t\t\titems = append(items, ResultItem{\n\t\t\t\tName:      alias,\n\t\t\t\tValueType: ex.ValueType,\n\t\t\t\tExpr: Expr{\n\t\t\t\t\t// This is a trick. This Expr is referred as subquery.\n\t\t\t\t\t// At the reference, it should be just referred as alias name instead of s.Raw.\n\t\t\t\t\tRaw:       QuoteString(alias),\n\t\t\t\t\tValueType: ex.ValueType,\n\t\t\t\t},\n\t\t\t})\n\t\t\texprs = append(exprs, fmt.Sprintf(\"%s AS %s\", ex.Raw, QuoteString(alias)))\n\n\t\tdefault:\n\t\t\treturn nil, \"\", status.Errorf(codes.Unimplemented, \"not supported %T in result set\", item)\n\t\t}\n\t}\n\n\tb.args = append(b.args, data...)\n\tseletQuery := strings.Join(exprs, \", \")\n\n\treturn items, seletQuery, nil\n}\n\nfunc (b *QueryBuilder) buildQuery(stmt *ast.Select) (string, error) {\n\tvar whereClause string\n\tif stmt.Where != nil {\n\t\ts, data, err := b.buildQueryWhereClause(stmt.Where)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\twhereClause = s\n\t\tb.args = append(b.args, data...)\n\t}\n\n\tvar groupByClause string\n\tif stmt.GroupBy != nil {\n\t\ts, data, err := b.buildQueryGroupByClause(stmt.GroupBy)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tgroupByClause = s\n\t\tb.args = append(b.args, data...)\n\t}\n\n\tvar havingClause string\n\tif stmt.Having != nil {\n\t\tex, err := b.buildExpr(stmt.Having.Expr)\n\t\tif err != nil {\n\t\t\treturn \"\", wrapExprError(err, stmt.Having.Expr, \"Having\")\n\t\t}\n\t\thavingClause = ex.Raw\n\t\tb.args = append(b.args, ex.Args...)\n\t}\n\n\tvar query string\n\tif whereClause != \"\" {\n\t\tquery += fmt.Sprintf(\" WHERE %s\", whereClause)\n\t}\n\tif groupByClause != \"\" {\n\t\tquery += fmt.Sprintf(\" GROUP BY %s\", groupByClause)\n\t}\n\tif havingClause != \"\" {\n\t\tquery += fmt.Sprintf(\" HAVING %s\", havingClause)\n\t}\n\t// Note: OrderBy and Limit are now handled at the Query level\n\n\treturn query, nil\n}\n\nfunc (b *QueryBuilder) buildQueryWhereClause(where *ast.Where) (string, []interface{}, error) {\n\tex, err := b.buildExpr(where.Expr)\n\tif err != nil {\n\t\treturn \"\", nil, wrapExprError(err, where.Expr, \"Building WHERE clause error\")\n\t}\n\treturn ex.Raw, ex.Args, nil\n}\n\nfunc (b *QueryBuilder) buildQueryGroupByClause(groupby *ast.GroupBy) (string, []interface{}, error) {\n\tvar groupByClause []string\n\tvar args []interface{}\n\n\tfor _, expr := range groupby.Exprs {\n\t\tex, err := b.buildExpr(expr)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, wrapExprError(err, expr, \"Building GROUP BY error\")\n\t\t}\n\t\tgroupByClause = append(groupByClause, ex.Raw)\n\t\targs = append(args, ex.Args...)\n\t}\n\n\treturn strings.Join(groupByClause, \", \"), args, nil\n}\n\nfunc (b *QueryBuilder) buildQueryOrderByClause(orderby *ast.OrderBy, view *TableView) (string, []interface{}, error) {\n\tvar orderByClause []string\n\tvar data []interface{}\n\n\tfor _, item := range orderby.Items {\n\t\t// TODO: use b.buildExpr()\n\t\tvar expr Expr\n\t\tswitch e := item.Expr.(type) {\n\t\tcase *ast.Ident:\n\t\t\ti, ambiguous, notfound := view.Get(e.Name)\n\t\t\tif notfound {\n\t\t\t\treturn \"\", nil, newExprErrorf(nil, true, \"Unrecognized name: %s\", e.Name)\n\t\t\t}\n\t\t\tif ambiguous {\n\t\t\t\treturn \"\", nil, newExprErrorf(nil, true, \"Column name %s is ambiguous\", e.Name)\n\t\t\t}\n\t\t\texpr = i.Expr\n\t\tcase *ast.Path:\n\t\t\tex, err := b.buildExpr(item.Expr)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", nil, wrapExprError(err, item.Expr, \"OrderBy\")\n\t\t\t}\n\t\t\texpr = ex\n\t\t}\n\n\t\tcollate := \"\"\n\t\tif item.Collate != nil {\n\t\t\tswitch v := item.Collate.Value.(type) {\n\t\t\tcase *ast.Param:\n\t\t\t\tvv, ok := b.params[v.Name]\n\t\t\t\tif !ok {\n\t\t\t\t\treturn \"\", nil, fmt.Errorf(\"No parameter found for binding: %s\", v.Name)\n\t\t\t\t}\n\t\t\t\tcollate = \"?\"\n\t\t\t\tdata = append(data, vv)\n\t\t\tcase *ast.StringLiteral:\n\t\t\t\tcollate = v.Value\n\t\t\t}\n\t\t}\n\t\torderByClause = append(orderByClause, fmt.Sprintf(\"%s %s %s\", expr.Raw, collate, item.Dir))\n\t}\n\n\treturn strings.Join(orderByClause, \", \"), data, nil\n}\n\nfunc (b *QueryBuilder) buildQueryLimitOffset(limit *ast.Limit) (string, []interface{}, error) {\n\tvar data []interface{}\n\te, err := b.buildIntValue(limit.Count, \"LIMIT\")\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tlimitClause := e.Raw\n\tdata = append(data, e.Args...)\n\n\tif limit.Offset != nil {\n\t\te, err := b.buildIntValue(limit.Offset.Value, \"OFFSET\")\n\t\tif err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\t\tdata = append(data, e.Args...)\n\t\tlimitClause += fmt.Sprintf(\" OFFSET %s\", e.Raw)\n\t}\n\n\treturn limitClause, data, nil\n}\n\nfunc (b *QueryBuilder) buildIntValue(intValue ast.IntValue, caller string) (Expr, error) {\n\tswitch iv := intValue.(type) {\n\tcase *ast.Param:\n\t\tex, err := b.buildExpr(iv)\n\t\tif err != nil {\n\t\t\treturn NullExpr, wrapExprError(err, nil, \"buildIntValue\")\n\t\t}\n\t\tif ex.ValueType.Code != TCInt64 {\n\t\t\treturn NullExpr, newExprErrorf(nil, true, \"%s expects an integer literal or parameter\", caller)\n\t\t}\n\t\treturn ex, nil\n\n\tcase *ast.IntLiteral:\n\t\tex, err := b.buildExpr(iv)\n\t\tif err != nil {\n\t\t\treturn NullExpr, wrapExprError(err, nil, \"buildIntValue\")\n\t\t}\n\t\tif ex.ValueType.Code != TCInt64 {\n\t\t\treturn NullExpr, newExprErrorf(nil, true, \"%s expects an integer literal or parameter\", caller)\n\t\t}\n\t\treturn ex, nil\n\n\tcase *ast.CastIntValue:\n\t\t// CAST expression can be used but it seems not working actually.\n\t\treturn NullExpr, newExprErrorf(nil, true, \"handy-spanner: CAST in Limit/Offset seems not working in Spanner\")\n\tdefault:\n\t\treturn NullExpr, fmt.Errorf(\"unknown LIMIT type\")\n\t}\n}\n\nfunc (b *QueryBuilder) buildInCondition(cond ast.InCondition) (Expr, error) {\n\tswitch c := cond.(type) {\n\tcase *ast.ValuesInCondition:\n\t\tvar ss []string\n\t\tvar args []interface{}\n\t\tfor _, e := range c.Exprs {\n\t\t\tex, err := b.buildExpr(e)\n\t\t\tif err != nil {\n\t\t\t\treturn NullExpr, wrapExprError(err, e, \"IN condition\")\n\t\t\t}\n\n\t\t\traw := b.unkeysStruct(ex.Raw, ex.ValueType)\n\n\t\t\tss = append(ss, raw)\n\t\t\targs = append(args, ex.Args...)\n\t\t}\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: TCBool},\n\t\t\tRaw:       \"(\" + strings.Join(ss, \", \") + \")\",\n\t\t\tArgs:      args,\n\t\t}, nil\n\n\tcase *ast.SubQueryInCondition:\n\t\tquery, data, items, err := BuildQuery(b.db, b.tx, c.Query, b.params, false)\n\t\tif err != nil {\n\t\t\treturn NullExpr, newExprErrorf(nil, false, \"BuildQuery error for SubqueryInCondition: %v\", err)\n\t\t}\n\t\tif len(items) != 1 {\n\t\t\treturn NullExpr, newExprErrorf(nil, true, \"Subquery of type IN must have only one output column\")\n\t\t}\n\n\t\t// TODO: if subquery uses SELECT AS STRUCT, it needs to expand struct\n\n\t\treturn Expr{\n\t\t\tValueType: items[0].ValueType, // inherit ValueType from result item\n\t\t\tRaw:       fmt.Sprintf(\"(%s)\", query),\n\t\t\tArgs:      data,\n\t\t}, nil\n\n\tcase *ast.UnnestInCondition:\n\t\ts, err := b.buildUnnestExpr(c.Expr, false, false)\n\t\tif err != nil {\n\t\t\treturn NullExpr, err\n\t\t}\n\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: TCBool},\n\t\t\tRaw:       fmt.Sprintf(\"(%s)\", s.Raw),\n\t\t\tArgs:      s.Args,\n\t\t}, nil\n\tdefault:\n\t\treturn NullExpr, fmt.Errorf(\"not supported InCondition %T\", c)\n\t}\n}\n\n// buildUnnestExpr creates a table from UNNEST() expression.\n//\n// sqlite cannot handle array type and UNNEST() directly.\n// Array type is handled as JSON, so UNNEST canbe simulated by JSON_EACH().\n//\n// `UNNEST([a, b, c])` in spanner results in the following expression in sqlite:\n// `SELECT value JSON_EACH(JSON_ARRAY((a), (b), (c))`\n//\n// When array param is specified like `UNNEST(@foo)`, it expands all elements of the array like:\n// `SELECT value JSON_EACH(JSON_ARRAY((?), (?) (?)))`\n//\n// If offset for unnest is needed `UNNEST([a, b, c])` generates:\n// `SELECT value, key as offset JSON_EACH(JSON_ARRAY(a, b, c))`\nfunc (b *QueryBuilder) buildUnnestExpr(expr ast.Expr, offset bool, asview bool) (Expr, error) {\n\tuseSqliteJSON()\n\tex, err := b.buildExpr(expr)\n\tif err != nil {\n\t\treturn NullExpr, wrapExprError(err, expr, \"UNNEST\")\n\t}\n\n\tif ex.ValueType.Code != TCArray {\n\t\tif asview {\n\t\t\tmsg := \"Values referenced in UNNEST must be arrays. UNNEST contains expression of type %s\"\n\t\t\treturn NullExpr, newExprErrorf(expr, true, msg, ex.ValueType.Code)\n\t\t} else {\n\t\t\tmsg := \"Second argument of IN UNNEST must be an array but was %s\"\n\t\t\treturn NullExpr, newExprErrorf(expr, true, msg, ex.ValueType.Code)\n\t\t}\n\t}\n\n\tvar raw string\n\tif asview {\n\t\tif offset {\n\t\t\traw = fmt.Sprintf(\"SELECT value, key as offset FROM JSON_EACH(%s)\", ex.Raw)\n\t\t} else {\n\t\t\traw = fmt.Sprintf(\"SELECT value FROM JSON_EACH(%s)\", ex.Raw)\n\t\t}\n\t} else {\n\t\tvalue := b.unkeysStruct(\"value\", *ex.ValueType.ArrayType)\n\t\traw = fmt.Sprintf(\"SELECT %s FROM JSON_EACH(%s)\", value, ex.Raw)\n\t}\n\n\treturn Expr{\n\t\tValueType: ex.ValueType,\n\t\tRaw:       raw,\n\t\tArgs:      ex.Args,\n\t}, nil\n}\n\n// unkeysStruct removes \"keys\" from object for struct type.\n// This is required to compare struct values.\nfunc (b *QueryBuilder) unkeysStruct(raw string, vt ValueType) string {\n\tif vt.Code != TCStruct {\n\t\treturn raw\n\t}\n\n\treturn fmt.Sprintf(\"JSON_REMOVE(%s, '$.keys')\", raw)\n}\n\nfunc (b *QueryBuilder) expandParamByPlaceholders(v Value) (Expr, error) {\n\tswitch v.Data.(type) {\n\tcase nil, bool, int64, float64, string, []byte:\n\t\treturn Expr{\n\t\t\tValueType: v.Type,\n\t\t\tRaw:       \"?\",\n\t\t\tArgs:      []interface{}{v.Data},\n\t\t}, nil\n\tcase []bool, []int64, []float64, []string, [][]byte:\n\t\tuseSqliteJSON()\n\t\tvv := reflect.ValueOf(v.Data)\n\t\tn := vv.Len()\n\n\t\tplaceholders := make([]string, n)\n\t\targs := make([]interface{}, n)\n\t\tfor i := 0; i < n; i++ {\n\t\t\tplaceholders[i] = \"(?)\"\n\t\t\targs[i] = vv.Index(i).Interface()\n\t\t}\n\t\tvar raw string\n\t\tif n > 0 {\n\t\t\traw = \"JSON_ARRAY(\" + strings.Join(placeholders, \", \") + \")\"\n\t\t}\n\t\treturn Expr{\n\t\t\tValueType: v.Type, // TODO: check correct type or not\n\t\t\tRaw:       raw,\n\t\t\tArgs:      args,\n\t\t}, nil\n\n\tcase ArrayValue:\n\t\tuseSqliteJSON()\n\t\trv := reflect.ValueOf(v.Data.(ArrayValue).Elements())\n\t\tn := rv.Len()\n\n\t\tplaceholders := make([]string, n)\n\t\targs := make([]interface{}, n)\n\t\tfor i := 0; i < n; i++ {\n\t\t\tplaceholders[i] = \"(?)\"\n\t\t\trvv := rv.Index(i)\n\t\t\tif rvv.IsNil() {\n\t\t\t\targs[i] = nil\n\t\t\t} else {\n\t\t\t\targs[i] = rvv.Interface()\n\t\t\t}\n\t\t}\n\t\tvar raw string\n\t\tif n > 0 {\n\t\t\traw = \"JSON_ARRAY(\" + strings.Join(placeholders, \", \") + \")\"\n\t\t}\n\t\treturn Expr{\n\t\t\tValueType: v.Type, // TODO: check correct type or not\n\t\t\tRaw:       raw,\n\t\t\tArgs:      args,\n\t\t}, nil\n\t}\n\n\treturn NullExpr, fmt.Errorf(\"unexpected parameter type for expandParamByPlaceholders: %T\", v)\n}\n\nfunc (b *QueryBuilder) accessField(expr Expr, name string) (Expr, error) {\n\tif expr.ValueType.Code != TCStruct {\n\t\tmsg := \"Cannot access field %s on a value with type %s\"\n\t\treturn NullExpr, newExprErrorf(nil, true, msg, name, expr.ValueType)\n\t}\n\n\tst := expr.ValueType.StructType\n\n\tidx := -1\n\tfor i := range st.FieldNames {\n\t\tif st.FieldNames[i] == name {\n\t\t\tif idx != -1 {\n\t\t\t\treturn NullExpr, newExprErrorf(nil, true, \"Column name %s is ambiguous\", name)\n\t\t\t}\n\n\t\t\tidx = i\n\t\t}\n\t}\n\tif idx == -1 {\n\t\tif st.IsTable {\n\t\t\t// TODO: stop adhoc fix for unescape\n\t\t\t// This unescapes the expression if it is quoted\n\t\t\texp := expr.Raw\n\t\t\tif exp[0] == '`' && exp[len(exp)-1] == '`' {\n\t\t\t\texp = exp[1 : len(exp)-1]\n\t\t\t}\n\n\t\t\tmsg := \"Name %s not found inside %s\"\n\t\t\treturn NullExpr, newExprErrorf(nil, true, msg, name, exp)\n\t\t} else {\n\t\t\tmsg := \"Field name %s does not exist in %s\"\n\t\t\treturn NullExpr, newExprErrorf(nil, true, msg, name, expr.ValueType)\n\t\t}\n\t}\n\n\tvar raw string\n\tif st.IsTable {\n\t\traw = fmt.Sprintf(\"%s.%s\", expr.Raw, QuoteString(name))\n\t} else {\n\t\tuseSqliteJSON()\n\t\traw = fmt.Sprintf(\"JSON_EXTRACT(%s, '$.values[%d]')\", expr.Raw, idx)\n\t}\n\n\treturn Expr{\n\t\tValueType: *st.FieldTypes[idx],\n\t\tRaw:       raw,\n\t\tArgs:      expr.Args,\n\t}, nil\n}\n\nfunc (b *QueryBuilder) buildExpr(expr ast.Expr) (Expr, error) {\n\tswitch e := expr.(type) {\n\tcase *ast.UnaryExpr:\n\t\tex, err := b.buildExpr(e.Expr)\n\t\tif err != nil {\n\t\t\treturn NullExpr, wrapExprError(err, expr, \"Unary\")\n\t\t}\n\n\t\treturn Expr{\n\t\t\tValueType: ex.ValueType,\n\t\t\tRaw:       fmt.Sprintf(\"%s %s\", e.Op, ex.Raw),\n\t\t\tArgs:      ex.Args,\n\t\t}, nil\n\n\tcase *ast.BinaryExpr:\n\t\tleft, lerr := b.buildExpr(e.Left)\n\t\tright, rerr := b.buildExpr(e.Right)\n\n\t\tif lerr != nil {\n\t\t\treturn NullExpr, wrapExprError(lerr, expr, \"Left\")\n\t\t}\n\t\tif rerr != nil {\n\t\t\treturn NullExpr, wrapExprError(rerr, expr, \"Right\")\n\t\t}\n\n\t\tvar args []interface{}\n\t\targs = append(args, left.Args...)\n\t\targs = append(args, right.Args...)\n\n\t\topName := map[ast.BinaryOp]string{\n\t\t\tast.OpLess:         \"Less than\",\n\t\t\tast.OpGreater:      \"Greater than\",\n\t\t\tast.OpLessEqual:    \"Less than\",\n\t\t\tast.OpGreaterEqual: \"Greater than\",\n\t\t}\n\n\t\tvar vt ValueType\n\t\tswitch e.Op {\n\t\tcase ast.OpEqual, ast.OpNotEqual:\n\t\t\tif !isComparable(left.ValueType, right.ValueType) {\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, \"No matching signature for operator %s for argument types: %s, %s.\", e.Op, left.ValueType, right.ValueType)\n\t\t\t}\n\t\t\tvt = ValueType{\n\t\t\t\tCode: TCBool,\n\t\t\t}\n\n\t\tcase ast.OpLess, ast.OpGreater, ast.OpLessEqual, ast.OpGreaterEqual:\n\t\t\tif !isComparable(left.ValueType, right.ValueType) {\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, \"No matching signature for operator %s for argument types: %s, %s.\", e.Op, left.ValueType, right.ValueType)\n\t\t\t}\n\n\t\t\tif left.ValueType.Code == TCStruct {\n\t\t\t\tmsg := \"%s is not defined for arguments of type %s\"\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, msg, opName[e.Op], left.ValueType)\n\t\t\t}\n\n\t\t\tvt = ValueType{\n\t\t\t\tCode: TCBool,\n\t\t\t}\n\t\tcase ast.OpOr, ast.OpAnd:\n\t\t\tvt = ValueType{\n\t\t\t\tCode: TCBool,\n\t\t\t}\n\t\tcase ast.OpLike, ast.OpNotLike:\n\t\t\tvt = ValueType{\n\t\t\t\tCode: TCBool,\n\t\t\t}\n\t\tcase ast.OpBitOr, ast.OpBitXor, ast.OpBitAnd, ast.OpBitLeftShift, ast.OpBitRightShift:\n\t\t\tvt = ValueType{\n\t\t\t\tCode: TCInt64, // TODO\n\t\t\t}\n\t\tcase ast.OpAdd, ast.OpSub, ast.OpMul:\n\t\t\tcode := TCInt64\n\t\t\tif left.ValueType.Code == TCFloat64 || right.ValueType.Code == TCFloat64 {\n\t\t\t\tcode = TCFloat64\n\t\t\t}\n\t\t\tvt = ValueType{\n\t\t\t\tCode: code,\n\t\t\t}\n\t\tcase ast.OpDiv:\n\t\t\tleft.Raw = fmt.Sprintf(\"CAST(%s AS REAL)\", left.Raw)\n\t\t\tvt = ValueType{\n\t\t\t\tCode: TCFloat64,\n\t\t\t}\n\t\tdefault:\n\t\t\treturn NullExpr, fmt.Errorf(\"%T: unknown op %v\", e, e.Op)\n\t\t}\n\n\t\traw := fmt.Sprintf(\"%s %s %s\", left.Raw, e.Op, right.Raw)\n\t\tif e.Op == ast.OpBitXor {\n\t\t\traw = fmt.Sprintf(\"(%s | %s) - (%s & %s)\", left.Raw, right.Raw, left.Raw, right.Raw)\n\t\t\t// TODO: args\n\t\t}\n\t\tif e.Op == ast.OpEqual || e.Op == ast.OpNotEqual {\n\t\t\tleftRaw := b.unkeysStruct(left.Raw, left.ValueType)\n\t\t\trightRaw := b.unkeysStruct(right.Raw, right.ValueType)\n\t\t\traw = fmt.Sprintf(\"%s %s %s\", leftRaw, e.Op, rightRaw)\n\t\t}\n\n\t\treturn Expr{\n\t\t\tValueType: vt,\n\t\t\tRaw:       raw,\n\t\t\tArgs:      args,\n\t\t}, nil\n\n\tcase *ast.InExpr:\n\t\tleft, lerr := b.buildExpr(e.Left)\n\t\tright, rerr := b.buildInCondition(e.Right)\n\t\tif lerr != nil {\n\t\t\treturn NullExpr, wrapExprError(lerr, expr, \"Left\")\n\t\t}\n\t\tif rerr != nil {\n\t\t\treturn NullExpr, wrapExprError(rerr, expr, \"Right\")\n\t\t}\n\n\t\tvar args []interface{}\n\t\targs = append(args, left.Args...)\n\t\targs = append(args, right.Args...)\n\n\t\top := \"IN\"\n\t\tif e.Not {\n\t\t\top = \"NOT IN\"\n\t\t}\n\n\t\tleftRaw := b.unkeysStruct(left.Raw, left.ValueType)\n\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: TCBool},\n\t\t\tRaw:       fmt.Sprintf(\"%s %s %s\", leftRaw, op, right.Raw),\n\t\t\tArgs:      args,\n\t\t}, nil\n\n\tcase *ast.BetweenExpr:\n\t\tleft, lerr := b.buildExpr(e.Left)\n\t\trstart, rserr := b.buildExpr(e.RightStart)\n\t\trend, reerr := b.buildExpr(e.RightEnd)\n\t\tif lerr != nil {\n\t\t\treturn NullExpr, wrapExprError(lerr, expr, \"Left\")\n\t\t}\n\t\tif rserr != nil {\n\t\t\treturn NullExpr, wrapExprError(rserr, expr, \"RightStart\")\n\t\t}\n\t\tif reerr != nil {\n\t\t\treturn NullExpr, wrapExprError(reerr, expr, \"RightEnd\")\n\t\t}\n\n\t\tvar args []interface{}\n\t\targs = append(args, left.Args...)\n\t\targs = append(args, rstart.Args...)\n\t\targs = append(args, rend.Args...)\n\n\t\top := \"BETWEEN\"\n\t\tif e.Not {\n\t\t\top = \"NOT BETWEEN\"\n\t\t}\n\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: TCBool},\n\t\t\tRaw:       fmt.Sprintf(\"%s %s %s AND %s\", left.Raw, op, rstart.Raw, rend.Raw),\n\t\t\tArgs:      args,\n\t\t}, nil\n\n\tcase *ast.SelectorExpr:\n\t\tex, err := b.buildExpr(e.Expr)\n\t\tif err != nil {\n\t\t\treturn NullExpr, wrapExprError(err, expr, \"Expr\")\n\t\t}\n\n\t\tex2, err := b.accessField(ex, e.Ident.Name)\n\t\tif err != nil {\n\t\t\treturn NullExpr, wrapExprError(err, expr, \"Expr\")\n\t\t}\n\n\t\treturn Expr{\n\t\t\tValueType: ex2.ValueType,\n\t\t\tRaw:       ex2.Raw,\n\t\t\tArgs:      ex2.Args,\n\t\t}, nil\n\n\tcase *ast.IndexExpr:\n\t\tuseSqliteJSON()\n\t\tex1, err := b.buildExpr(e.Expr)\n\t\tif err != nil {\n\t\t\treturn NullExpr, wrapExprError(err, expr, \"Expr\")\n\t\t}\n\n\t\tvar indexExpr ast.Expr\n\t\tswitch typ := e.Index.(type) {\n\t\tcase *ast.ExprArg:\n\t\t\tindexExpr = typ.Expr\n\t\tcase *ast.SubscriptSpecifierKeyword:\n\t\t\tindexExpr = typ.Expr\n\t\tdefault:\n\t\t\treturn NullExpr, newExprErrorf(expr, true, \"Unknown index expression: %T\", typ)\n\t\t}\n\t\tex2, err := b.buildExpr(indexExpr)\n\t\tif err != nil {\n\t\t\treturn NullExpr, wrapExprError(err, expr, \"Index\")\n\t\t}\n\n\t\tif ex1.ValueType.Code != TCArray {\n\t\t\tmsg := \"Element access using [] is not supported on values of type %s\"\n\t\t\treturn NullExpr, newExprErrorf(expr, true, msg, ex1.ValueType)\n\t\t}\n\t\tif ex2.ValueType.Code != TCInt64 {\n\t\t\tmsg := \"Array position in [] must be coercible to INT64 type, but has type %s\"\n\t\t\treturn NullExpr, newExprErrorf(expr, true, msg, ex2.ValueType)\n\t\t}\n\n\t\tvar args []interface{}\n\t\targs = append(args, ex1.Args...)\n\t\targs = append(args, ex2.Args...)\n\n\t\tvar raw string\n\t\tif k, ok := e.Index.(*ast.SubscriptSpecifierKeyword); ok && k.Keyword == ast.PositionKeywordOrdinal {\n\t\t\traw = fmt.Sprintf(\"JSON_EXTRACT(%s, '$[' || (%s-1) || ']')\", ex1.Raw, ex2.Raw)\n\t\t} else {\n\t\t\traw = fmt.Sprintf(\"JSON_EXTRACT(%s, '$[' || %s || ']')\", ex1.Raw, ex2.Raw)\n\t\t}\n\n\t\treturn Expr{\n\t\t\tValueType: *ex1.ValueType.ArrayType,\n\t\t\tRaw:       raw,\n\t\t\tArgs:      args,\n\t\t}, nil\n\n\tcase *ast.IsNullExpr:\n\t\tleft, lerr := b.buildExpr(e.Left)\n\t\tif lerr != nil {\n\t\t\treturn NullExpr, wrapExprError(lerr, expr, \"Left\")\n\t\t}\n\n\t\top := \"IS NULL\"\n\t\tif e.Not {\n\t\t\top = \"IS NOT NULL\"\n\t\t}\n\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: TCBool},\n\t\t\tRaw:       fmt.Sprintf(\"%s %s\", left.Raw, op),\n\t\t\tArgs:      left.Args,\n\t\t}, nil\n\n\tcase *ast.IsBoolExpr:\n\t\tleft, lerr := b.buildExpr(e.Left)\n\t\tif lerr != nil {\n\t\t\treturn NullExpr, wrapExprError(lerr, expr, \"Left\")\n\t\t}\n\n\t\top := \"IS\"\n\t\tif e.Not {\n\t\t\top = \"IS NOT\"\n\t\t}\n\t\tb := \"TRUE\"\n\t\tif e.Right {\n\t\t\tb = \"FALSE\"\n\t\t}\n\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: TCBool},\n\t\t\tRaw:       fmt.Sprintf(\"%s %s %s\", left.Raw, op, b),\n\t\t\tArgs:      left.Args,\n\t\t}, nil\n\n\tcase *ast.CallExpr:\n\t\tname := strings.ToUpper(e.Func.Idents[0].Name)\n\t\tif v, ok := customFunctionNamesMap[name]; ok {\n\t\t\tname = v\n\t\t}\n\n\t\tfn, ok := customFunctions[name]\n\t\tif !ok {\n\t\t\treturn NullExpr, newExprErrorf(expr, false, \"unsupported CALL function: %s\", name)\n\t\t}\n\n\t\t// TODO: distinct\n\n\t\t// when fn.NArgs < 0, args is variadic\n\t\tif fn.NArgs >= 0 && fn.NArgs != len(e.Args) {\n\t\t\treturn NullExpr, newExprErrorf(expr, true, \"%s requires %d arguments\", name, fn.NArgs)\n\t\t}\n\n\t\tvar args []interface{}\n\t\tvar ss []string\n\t\tvar vts []ValueType\n\t\tfor _, arg := range e.Args {\n\t\t\tvar expr ast.Expr\n\t\t\tswitch arg := arg.(type) {\n\t\t\tcase *ast.ExprArg:\n\t\t\t\texpr = arg.Expr\n\t\t\tcase *ast.SequenceArg:\n\t\t\t\tmsg := `Unsupported query shape: SequenceArg in function call is not supported yet.`\n\t\t\t\treturn NullExpr, newExprUnimplementedErrorf(expr, msg)\n\t\t\tdefault:\n\t\t\t\tmsg := `Invalid query shape: unknown argument type was detected.`\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, msg)\n\t\t\t}\n\n\t\t\tex, err := b.buildExpr(expr)\n\t\t\tif err != nil {\n\t\t\t\treturn NullExpr, wrapExprError(err, expr, \"Args\")\n\t\t\t}\n\t\t\targs = append(args, ex.Args...)\n\t\t\tss = append(ss, ex.Raw)\n\t\t\tvts = append(vts, ex.ValueType)\n\t\t}\n\n\t\tif ok := fn.ArgTypes(vts); !ok {\n\t\t\treturn NullExpr, newExprErrorf(expr, true, \"arguments does not match for %s\", name)\n\t\t}\n\n\t\tvar distinct string\n\t\tif e.Distinct {\n\t\t\tdistinct = \"DISTINCT \"\n\t\t}\n\n\t\treturn Expr{\n\t\t\tValueType: fn.ReturnType(vts),\n\t\t\tRaw:       fmt.Sprintf(`%s(%s%s)`, name, distinct, strings.Join(ss, \", \")),\n\t\t\tArgs:      args,\n\t\t}, nil\n\n\tcase *ast.CountStarExpr:\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: TCInt64},\n\t\t\tRaw:       \"COUNT(*)\",\n\t\t}, nil\n\n\tcase *ast.CastExpr:\n\t\tex, err := b.buildExpr(e.Expr)\n\t\tif err != nil {\n\t\t\treturn NullExpr, wrapExprError(err, expr, \"Cast\")\n\t\t}\n\n\t\ttarget := astTypeToValueType(e.Type)\n\n\t\treturn castTo(ex, target)\n\tcase *ast.ExtractExpr:\n\t\tex, err := b.buildExpr(e.Expr)\n\t\tif err != nil {\n\t\t\treturn NullExpr, wrapExprError(err, expr, \"Paren\")\n\t\t}\n\n\t\targs := ex.Args\n\n\t\tvar raw string\n\t\tcode := TCInt64\n\t\tswitch ex.ValueType.Code {\n\t\tcase TCTimestamp:\n\t\t\tif e.AtTimeZone == nil {\n\t\t\t\tmsg := fmt.Sprintf(\"handy-spanner: please specify timezone explicitly. Use %q for default timezone\", defaultTimeZone)\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, msg)\n\t\t\t}\n\n\t\t\ttz, err := b.buildExpr(e.AtTimeZone.Expr)\n\t\t\tif err != nil {\n\t\t\t\treturn NullExpr, wrapExprError(err, expr, \"AT TIME ZONE\")\n\t\t\t}\n\t\t\tif tz.ValueType.Code != TCString {\n\t\t\t\tmsg := \"No matching signature for function EXTRACT for argument types: DATE_TIME_PART FROM TIMESTAMP AT TIME ZONE %s. Supported signatures: EXTRACT(DATE_TIME_PART FROM DATE); EXTRACT(DATE_TIME_PART FROM TIMESTAMP [AT TIME ZONE STRING])\"\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, msg, tz.ValueType)\n\t\t\t}\n\t\t\targs = append(args, tz.Args...)\n\n\t\t\tpart := strings.ToUpper(e.Part.Name)\n\t\t\tswitch part {\n\t\t\tcase \"NANOSECOND\",\n\t\t\t\t\"MICROSECOND\",\n\t\t\t\t\"MILLISECOND\",\n\t\t\t\t\"SECOND\",\n\t\t\t\t\"MINUTE\",\n\t\t\t\t\"HOUR\",\n\t\t\t\t\"DAYOFWEEK\",\n\t\t\t\t\"DAY\",\n\t\t\t\t\"DAYOFYEAR\",\n\t\t\t\t\"WEEK\",\n\t\t\t\t\"ISOWEEK\",\n\t\t\t\t\"MONTH\",\n\t\t\t\t\"QUARTER\",\n\t\t\t\t\"YEAR\",\n\t\t\t\t\"ISOYEAR\":\n\t\t\t\traw = fmt.Sprintf(\"___EXTRACT_FROM_TIMESTAMP(%q, %s, %s)\", part, ex.Raw, tz.Raw)\n\n\t\t\tcase \"DATE\":\n\t\t\t\tcode = TCDate\n\t\t\t\traw = fmt.Sprintf(\"DATE(%s)\", ex.Raw)\n\t\t\tdefault:\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, \"A valid date part name is required but found %s\", e.Part.Name)\n\t\t\t}\n\n\t\tcase TCDate:\n\t\t\tif e.AtTimeZone != nil {\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, \"EXTRACT from DATE does not support AT TIME ZONE\")\n\t\t\t}\n\n\t\t\tpart := strings.ToUpper(e.Part.Name)\n\t\t\tswitch part {\n\t\t\tcase \"DAYOFWEEK\",\n\t\t\t\t\"DAY\",\n\t\t\t\t\"DAYOFYEAR\",\n\t\t\t\t\"WEEK\",\n\t\t\t\t\"ISOWEEK\",\n\t\t\t\t\"MONTH\",\n\t\t\t\t\"QUARTER\",\n\t\t\t\t\"YEAR\",\n\t\t\t\t\"ISOYEAR\":\n\t\t\t\traw = fmt.Sprintf(\"___EXTRACT_FROM_DATE(%q, %s)\", part, ex.Raw)\n\n\t\t\tcase \"NANOSECOND\",\n\t\t\t\t\"MICROSECOND\",\n\t\t\t\t\"MILLISECOND\",\n\t\t\t\t\"SECOND\",\n\t\t\t\t\"MINUTE\",\n\t\t\t\t\"HOUR\",\n\t\t\t\t\"DATE\":\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, \"EXTRACT from DATE does not support the %s date part\", part)\n\t\t\tdefault:\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, \"A valid date part name is required but found %s\", e.Part.Name)\n\t\t\t}\n\n\t\tdefault:\n\t\t\treturn NullExpr, newExprErrorf(expr, true, \"EXTRACT does not support literal %s arguments\", ex.ValueType)\n\t\t}\n\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: code},\n\t\t\tRaw:       raw,\n\t\t\tArgs:      args,\n\t\t}, nil\n\n\tcase *ast.CaseExpr:\n\t\treturn NullExpr, newExprErrorf(expr, false, \"Case not supported yet\")\n\n\tcase *ast.ParenExpr:\n\t\tex, err := b.buildExpr(e.Expr)\n\t\tif err != nil {\n\t\t\treturn NullExpr, wrapExprError(err, expr, \"Paren\")\n\t\t}\n\n\t\treturn Expr{\n\t\t\tValueType: ex.ValueType,\n\t\t\tRaw:       fmt.Sprintf(\"(%s)\", ex.Raw),\n\t\t\tArgs:      ex.Args,\n\t\t}, nil\n\n\tcase *ast.ScalarSubQuery:\n\t\tquery, args, items, err := BuildQuery(b.db, b.tx, e.Query, b.params, false)\n\t\tif err != nil {\n\t\t\treturn NullExpr, wrapExprError(err, expr, \"Scalar\")\n\t\t}\n\t\tif len(items) != 1 {\n\t\t\treturn NullExpr, newExprErrorf(expr, true, \"Scalar subquery cannot have more than one column unless using SELECT AS STRUCT to build STRUCT values\")\n\t\t}\n\t\treturn Expr{\n\t\t\tValueType: items[0].ValueType, // inherit ValueType from result item\n\t\t\tRaw:       fmt.Sprintf(\"(%s)\", query),\n\t\t\tArgs:      args,\n\t\t}, nil\n\n\tcase *ast.ArraySubQuery:\n\t\tuseSqliteJSON()\n\t\tquery, args, items, err := BuildQuery(b.db, b.tx, e.Query, b.params, true)\n\t\tif err != nil {\n\t\t\treturn NullExpr, wrapExprError(err, expr, \"Array\")\n\t\t}\n\t\tif len(items) != 1 {\n\t\t\treturn NullExpr, newExprErrorf(expr, true, \"ARRAY subquery cannot have more than one column unless using SELECT AS STRUCT to build STRUCT values\")\n\t\t}\n\t\tif items[0].ValueType.Code == TCArray {\n\t\t\tmsg := \"Cannot use array subquery with column of type %s because nested arrays are not supported\"\n\t\t\treturn NullExpr, newExprErrorf(expr, true, msg, items[0].ValueType)\n\t\t}\n\n\t\treturn Expr{\n\t\t\tValueType: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &items[0].ValueType,\n\t\t\t},\n\t\t\tRaw:  fmt.Sprintf(\"(SELECT JSON_GROUP_ARRAY(%s) FROM (%s))\", items[0].Expr.Raw, query),\n\t\t\tArgs: args,\n\t\t}, nil\n\n\tcase *ast.ExistsSubQuery:\n\t\tquery, args, _, err := BuildQuery(b.db, b.tx, e.Query, b.params, false)\n\t\tif err != nil {\n\t\t\treturn NullExpr, newExprErrorf(expr, false, \"BuildQuery error for %T: %v\", err, e)\n\t\t}\n\t\treturn Expr{\n\t\t\tValueType: ValueType{\n\t\t\t\tCode: TCBool,\n\t\t\t},\n\t\t\tRaw:  fmt.Sprintf(\"EXISTS(%s)\", query),\n\t\t\tArgs: args,\n\t\t}, nil\n\n\tcase *ast.ArrayLiteral:\n\t\tuseSqliteJSON()\n\t\tvar args []interface{}\n\t\tvar ss []string\n\t\tvar vts []ValueType\n\n\t\tvar nestedStrType *ast.StructType\n\t\tif e.Type != nil {\n\t\t\tif st, ok := e.Type.(*ast.StructType); ok {\n\t\t\t\tnestedStrType = st\n\t\t\t}\n\t\t}\n\n\t\tfor i := range e.Values {\n\t\t\t// If the element is struct, need to populate the struct type.\n\t\t\t// Because only top node knows the details of the type if it's a compound type.\n\t\t\tif str, ok := e.Values[i].(*ast.TypedStructLiteral); ok && nestedStrType != nil {\n\t\t\t\tstr.Fields = nestedStrType.Fields\n\t\t\t}\n\n\t\t\tex, err := b.buildExpr(e.Values[i])\n\t\t\tif err != nil {\n\t\t\t\treturn NullExpr, wrapExprError(err, expr, \"ArrayLiteral\")\n\t\t\t}\n\n\t\t\t// If the element is tuple struct literal, need to populate the filed name.\n\t\t\tif _, ok := e.Values[i].(*ast.TupleStructLiteral); ok && nestedStrType != nil {\n\t\t\t\tif len(ex.ValueType.StructType.FieldNames) != len(nestedStrType.Fields) {\n\t\t\t\t\treturn NullExpr, newExprErrorf(expr, true, \"STRUCT type has %d fields but nested STRUCT type has %d fields\",\n\t\t\t\t\t\tlen(ex.ValueType.StructType.FieldNames), len(nestedStrType.Fields))\n\t\t\t\t}\n\t\t\t\tfor key, field := range nestedStrType.Fields {\n\t\t\t\t\tif field.Ident == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tex.ValueType.StructType.FieldNames[key] = field.Ident.Name\n\t\t\t\t}\n\t\t\t}\n\n\t\t\targs = append(args, ex.Args...)\n\t\t\tss = append(ss, ex.Raw)\n\t\t\tvts = append(vts, ex.ValueType)\n\t\t}\n\n\t\tvt, err := decideArrayElementsValueType(vts...)\n\t\tif err != nil {\n\t\t\treturn NullExpr, newExprErrorf(expr, true, err.Error())\n\t\t}\n\n\t\tif vt.Code == TCArray {\n\t\t\tmsg := \"Cannot construct array with element type %s because nested arrays are not supported\"\n\t\t\treturn NullExpr, newExprErrorf(expr, true, msg, vt)\n\t\t}\n\n\t\t// TODO: allow to use both Int64 and Float64\n\n\t\treturn Expr{\n\t\t\tValueType: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &vt,\n\t\t\t},\n\t\t\tRaw:  fmt.Sprintf(\"JSON_ARRAY(%s)\", strings.Join(ss, \", \")),\n\t\t\tArgs: args,\n\t\t}, nil\n\n\tcase *ast.TupleStructLiteral:\n\t\tuseSqliteJSON()\n\t\t// TupleStructLiteral is for unnamed struct syntax like (1, 2)\n\t\tvar names []string\n\t\tvar vt ValueType\n\n\t\t// For tuple syntax, all fields are unnamed (empty string)\n\t\tfor i := 0; i < len(e.Values); i++ {\n\t\t\tnames = append(names, `\"\"`)\n\t\t}\n\n\t\t// Initialize struct type\n\t\tvt = ValueType{\n\t\t\tCode:       TCStruct,\n\t\t\tStructType: &StructType{},\n\t\t}\n\n\t\tnamesObj := fmt.Sprintf(\"JSON_ARRAY(%s)\", strings.Join(names, \", \"))\n\n\t\tvar args []interface{}\n\t\tvar values []string\n\t\tfor _, v := range e.Values {\n\t\t\tex, err := b.buildExpr(v)\n\t\t\tif err != nil {\n\t\t\t\treturn NullExpr, wrapExprError(err, expr, \"Values\")\n\t\t\t}\n\t\t\targs = append(args, ex.Args...)\n\n\t\t\tif ex.ValueType.Code == TCStruct {\n\t\t\t\tmsg := `Unsupported query shape: A struct value cannot be returned as a column value. Rewrite the query to flatten the struct fields in the result.`\n\t\t\t\treturn NullExpr, newExprUnimplementedErrorf(expr, msg)\n\t\t\t}\n\n\t\t\t// For tuple syntax, field names are empty and types are inferred from values\n\t\t\tvt.StructType.FieldNames = append(vt.StructType.FieldNames, \"\")\n\t\t\tvt.StructType.FieldTypes = append(vt.StructType.FieldTypes, &ex.ValueType)\n\t\t\tvalues = append(values, ex.Raw)\n\t\t}\n\t\tvaluesObj := fmt.Sprintf(\"JSON_ARRAY(%s)\", strings.Join(values, \", \"))\n\t\traw := fmt.Sprintf(`JSON_OBJECT(\"keys\", %s, \"values\", %s)`, namesObj, valuesObj)\n\n\t\treturn Expr{\n\t\t\tRaw:       raw,\n\t\t\tValueType: vt,\n\t\t\tArgs:      args,\n\t\t}, nil\n\n\tcase *ast.TypedStructLiteral:\n\t\tuseSqliteJSON()\n\t\tvar names []string\n\t\tvar vt ValueType\n\n\t\tif len(e.Fields) != len(e.Values) {\n\t\t\treturn NullExpr, newExprErrorf(expr, true, \"STRUCT type has %d fields but constructor call has %d fields\", len(e.Fields), len(e.Values))\n\t\t}\n\t\tvt = astTypeToValueType(&ast.StructType{\n\t\t\tFields: e.Fields,\n\t\t})\n\t\tfor _, name := range vt.StructType.FieldNames {\n\t\t\tnames = append(names, `\"`+name+`\"`)\n\t\t}\n\n\t\tnamesObj := fmt.Sprintf(\"JSON_ARRAY(%s)\", strings.Join(names, \", \"))\n\n\t\tvar args []interface{}\n\t\tvar values []string\n\t\tfor i, v := range e.Values {\n\t\t\tex, err := b.buildExpr(v)\n\t\t\tif err != nil {\n\t\t\t\treturn NullExpr, wrapExprError(err, expr, \"Values\")\n\t\t\t}\n\t\t\targs = append(args, ex.Args...)\n\n\t\t\tif ex.ValueType.Code == TCStruct {\n\t\t\t\tmsg := `Unsupported query shape: A struct value cannot be returned as a column value. Rewrite the query to flatten the struct fields in the result.`\n\t\t\t\treturn NullExpr, newExprUnimplementedErrorf(expr, msg)\n\t\t\t}\n\n\t\t\tif !compareValueType(ex.ValueType, *vt.StructType.FieldTypes[i]) {\n\t\t\t\tmsg := \"Struct field %d has type literal %s which does not coerce to %s\"\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, msg, i+1, ex.ValueType, *vt.StructType.FieldTypes[i])\n\t\t\t}\n\t\t\tvalues = append(values, ex.Raw)\n\t\t}\n\t\tvaluesObj := fmt.Sprintf(\"JSON_ARRAY(%s)\", strings.Join(values, \", \"))\n\t\traw := fmt.Sprintf(`JSON_OBJECT(\"keys\", %s, \"values\", %s)`, namesObj, valuesObj)\n\n\t\treturn Expr{\n\t\t\tRaw:       raw,\n\t\t\tValueType: vt,\n\t\t\tArgs:      args,\n\t\t}, nil\n\n\tcase *ast.NullLiteral:\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: TCInt64},\n\t\t\tRaw:       \"NULL\",\n\t\t}, nil\n\n\tcase *ast.BoolLiteral:\n\t\tif e.Value {\n\t\t\treturn Expr{\n\t\t\t\tValueType: ValueType{Code: TCBool},\n\t\t\t\tRaw:       \"TRUE\",\n\t\t\t}, nil\n\t\t}\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: TCBool},\n\t\t\tRaw:       \"FALSE\",\n\t\t}, nil\n\n\tcase *ast.FloatLiteral:\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: TCFloat64},\n\t\t\tRaw:       e.Value,\n\t\t}, nil\n\n\tcase *ast.IntLiteral:\n\t\tn, err := strconv.ParseInt(e.Value, 0, 64)\n\t\tif err != nil {\n\t\t\treturn NullExpr, newExprErrorf(expr, false, \"unexpected format %q as int64: %v\", e.Value, err)\n\t\t}\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: TCInt64},\n\t\t\tRaw:       strconv.FormatInt(n, 10),\n\t\t}, nil\n\n\tcase *ast.StringLiteral:\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: TCString},\n\t\t\tRaw:       fmt.Sprintf(\"%q\", e.Value),\n\t\t}, nil\n\n\tcase *ast.BytesLiteral:\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: TCBytes},\n\t\t\tRaw:       `\"` + string(e.Value) + `\"`,\n\t\t}, nil\n\n\tcase *ast.DateLiteral:\n\t\tt, ok := parseDateLiteral(e.Value.Value)\n\t\tif !ok {\n\t\t\treturn NullExpr, newExprErrorf(expr, true, \"Invalid DATE literal\")\n\t\t}\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: TCDate},\n\t\t\tRaw:       `\"` + t.Format(\"2006-01-02\") + `\"`,\n\t\t}, nil\n\n\tcase *ast.TimestampLiteral:\n\t\tt, ok := parseTimestampLiteral(e.Value.Value)\n\t\tif !ok {\n\t\t\treturn NullExpr, newExprErrorf(expr, true, \"Invalid TIMESTAMP literal\")\n\t\t}\n\t\treturn Expr{\n\t\t\tValueType: ValueType{Code: TCTimestamp},\n\t\t\tRaw:       `\"` + t.Format(time.RFC3339Nano) + `\"`,\n\t\t}, nil\n\n\tcase *ast.Param:\n\t\tv, ok := b.params[e.Name]\n\t\tif !ok {\n\t\t\treturn NullExpr, newExprErrorf(expr, true, \"No parameter found for binding: %s\", e.Name)\n\t\t}\n\t\treturn b.expandParamByPlaceholders(v)\n\n\tcase *ast.Ident:\n\t\ttbl, ok := b.views[e.Name]\n\t\tif ok {\n\t\t\treturn Expr{\n\t\t\t\tValueType: ValueType{\n\t\t\t\t\tCode:       TCStruct,\n\t\t\t\t\tStructType: tbl.ToStruct(),\n\t\t\t\t},\n\t\t\t\tRaw: QuoteString(e.Name),\n\t\t\t}, nil\n\t\t} else {\n\t\t\tif b.rootView == nil {\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, \"Unrecognized name: %s\", e.Name)\n\t\t\t}\n\n\t\t\titem, ambiguous, notfound := b.rootView.Get(e.Name)\n\t\t\tif notfound {\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, \"Unrecognized name: %s\", e.Name)\n\t\t\t}\n\t\t\tif ambiguous {\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, \"Column name %s is ambiguous\", e.Name)\n\t\t\t}\n\t\t\treturn item.Expr, nil\n\t\t}\n\tcase *ast.Path:\n\t\tfirstName := e.Idents[0].Name\n\t\tvar remains []*ast.Ident\n\t\tvar curExpr Expr\n\n\t\t// Look tables first\n\t\ttbl, ok := b.views[firstName]\n\t\tif ok {\n\t\t\tcurExpr = Expr{\n\t\t\t\tValueType: ValueType{\n\t\t\t\t\tCode:       TCStruct,\n\t\t\t\t\tStructType: tbl.ToStruct(),\n\t\t\t\t},\n\t\t\t\tRaw: QuoteString(firstName),\n\t\t\t}\n\t\t} else {\n\t\t\t// If not found in tables, look the results items of root\n\t\t\tif b.rootView == nil {\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, \"Unrecognized name: %s\", firstName)\n\t\t\t}\n\n\t\t\titem, ambiguous, notfound := b.rootView.Get(firstName)\n\t\t\tif notfound {\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, \"Unrecognized name: %s\", firstName)\n\t\t\t}\n\t\t\tif ambiguous {\n\t\t\t\treturn NullExpr, newExprErrorf(expr, true, \"Column name %s is ambiguous\", firstName)\n\t\t\t}\n\n\t\t\tcurExpr = item.Expr\n\t\t}\n\t\tremains = e.Idents[1:]\n\n\t\tfor _, ident := range remains {\n\t\t\tnext, err := b.accessField(curExpr, ident.Name)\n\t\t\tif err != nil {\n\t\t\t\treturn NullExpr, wrapExprError(err, expr, \"Path\")\n\t\t\t}\n\n\t\t\tcurExpr = next\n\t\t}\n\n\t\treturn curExpr, nil\n\tdefault:\n\t\treturn NullExpr, newExprErrorf(expr, false, \"unknown expression\")\n\t}\n}\n\ntype exprError struct {\n\texpr ast.Expr\n\tmsg  string\n\n\tinvalid       bool\n\tunimplemented bool\n}\n\nfunc (e *exprError) Error() string {\n\treturn fmt.Sprintf(\"%T: %s\", e.expr, e.msg)\n}\n\nfunc (e exprError) GRPCStatus() *status.Status {\n\tcode := codes.Unknown\n\tif e.invalid {\n\t\tcode = codes.InvalidArgument\n\t}\n\tif e.unimplemented {\n\t\tcode = codes.Unimplemented\n\t}\n\treturn status.New(code, e.msg)\n}\n\nfunc newExprErrorf(expr ast.Expr, invalid bool, format string, a ...interface{}) error {\n\treturn &exprError{\n\t\texpr:    expr,\n\t\tmsg:     fmt.Sprintf(format, a...),\n\t\tinvalid: invalid,\n\t}\n}\n\nfunc newExprUnimplementedErrorf(expr ast.Expr, format string, a ...interface{}) error {\n\treturn &exprError{\n\t\texpr:          expr,\n\t\tmsg:           fmt.Sprintf(format, a...),\n\t\tunimplemented: true,\n\t}\n}\n\nfunc wrapExprError(err error, expr ast.Expr, msg string) error {\n\texprErr, ok := err.(*exprError)\n\tif !ok {\n\t\treturn fmt.Errorf(\"unknown error in wrapExprError: %v\", err)\n\t}\n\n\t// if error is invalid or unimplemented it is invalig argument, so return it as is\n\tif exprErr.invalid || exprErr.unimplemented {\n\t\treturn err\n\t}\n\n\treturn newExprErrorf(expr, false, \"%s, %s\", msg, err.Error())\n}\n\nfunc astTypeToValueType(astType ast.Type) ValueType {\n\tswitch t := astType.(type) {\n\tcase *ast.SimpleType:\n\t\treturn ValueType{Code: astTypeToTypeCode(t.Name)}\n\tcase *ast.ArrayType:\n\t\tvt := astTypeToValueType(t.Item)\n\t\treturn ValueType{\n\t\t\tCode:      TCArray,\n\t\t\tArrayType: &vt,\n\t\t}\n\tcase *ast.StructType:\n\t\tnames := make([]string, 0, len(t.Fields))\n\t\ttypes := make([]*ValueType, 0, len(t.Fields))\n\n\t\tfor _, field := range t.Fields {\n\t\t\tvar name string\n\t\t\tif field.Ident != nil {\n\t\t\t\tname = field.Ident.Name\n\t\t\t}\n\t\t\tnames = append(names, name)\n\n\t\t\tvt := astTypeToValueType(field.Type)\n\t\t\ttypes = append(types, &vt)\n\n\t\t}\n\t\treturn ValueType{\n\t\t\tCode: TCStruct,\n\t\t\tStructType: &StructType{\n\t\t\t\tFieldNames: names,\n\t\t\t\tFieldTypes: types,\n\t\t\t},\n\t\t}\n\t}\n\n\tpanic(\"unknown type\")\n}\n\nfunc parseDateLiteral(s string) (time.Time, bool) {\n\tif t, err := time.ParseInLocation(\"2006-1-2\", s, parseLocation); err == nil {\n\t\treturn t, true\n\t}\n\n\treturn time.Time{}, false\n}\n\nfunc parseTimestampLiteral(s string) (time.Time, bool) {\n\t// TODO: cannot parse these format\n\t// 1999-01-02 12:02:03.123456789+3\n\t// 1999-01-02 12:02:03.123456789 UTC\n\n\tif t, err := time.ParseInLocation(\"2006-1-2 15:4:5.999999999Z07:00\", s, parseLocation); err == nil {\n\t\treturn t.UTC(), true\n\t}\n\n\tif t, err := time.ParseInLocation(\"2006-1-2 15:4:5.999999999Z07\", s, parseLocation); err == nil {\n\t\treturn t.UTC(), true\n\t}\n\n\tif t, err := time.ParseInLocation(\"2006-1-2 15:4:5.999999999\", s, parseLocation); err == nil {\n\t\treturn t.UTC(), true\n\t}\n\n\tif t, err := time.ParseInLocation(\"2006-1-2T15:4:5.999999999Z07:00\", s, parseLocation); err == nil {\n\t\treturn t.UTC(), true\n\t}\n\n\tif t, err := time.ParseInLocation(\"2006-1-2T15:4:5.999999999Z07\", s, parseLocation); err == nil {\n\t\treturn t.UTC(), true\n\t}\n\n\tif t, err := time.ParseInLocation(\"2006-1-2T15:4:5.999999999\", s, parseLocation); err == nil {\n\t\treturn t.UTC(), true\n\t}\n\n\tif t, err := time.ParseInLocation(\"2006-1-2\", s, parseLocation); err == nil {\n\t\treturn t.UTC(), true\n\t}\n\n\treturn time.Time{}, false\n}\n\nfunc castTo(ex Expr, target ValueType) (Expr, error) {\n\tif target.Code == TCStruct {\n\t\treturn NullExpr, newExprErrorf(nil, false, \"Struct type is not supported in Cast\")\n\t}\n\n\traw := ex.Raw\n\tswitch ex.ValueType.Code {\n\tcase TCInt64:\n\t\tswitch target.Code {\n\t\tcase TCBool:\n\t\t\traw = fmt.Sprintf(\"___CAST_INT64_TO_BOOL(%s)\", raw)\n\t\tcase TCString:\n\t\t\traw = fmt.Sprintf(\"___CAST_INT64_TO_STRING(%s)\", raw)\n\t\tcase TCInt64:\n\t\t\t// do nothing\n\t\tcase TCFloat64:\n\t\t\traw = fmt.Sprintf(\"___CAST_INT64_TO_FLOAT64(%s)\", raw)\n\t\tdefault:\n\t\t\treturn NullExpr, newExprErrorf(nil, true, \"Invalid cast from %s to %s\", ex.ValueType.Code, target.Code)\n\t\t}\n\tcase TCFloat64:\n\t\tswitch target.Code {\n\t\tcase TCString:\n\t\t\traw = fmt.Sprintf(\"___CAST_FLOAT64_TO_STRING(%s)\", raw)\n\t\tcase TCInt64:\n\t\t\traw = fmt.Sprintf(\"___CAST_FLOAT64_TO_INT64(%s)\", raw)\n\t\tcase TCFloat64:\n\t\t\t// do nothing\n\t\tdefault:\n\t\t\treturn NullExpr, newExprErrorf(nil, true, \"Invalid cast from %s to %s\", ex.ValueType.Code, target.Code)\n\t\t}\n\n\tcase TCBool:\n\t\tswitch target.Code {\n\t\tcase TCString:\n\t\t\traw = fmt.Sprintf(\"___CAST_BOOL_TO_STRING(%s)\", raw)\n\t\tcase TCBool:\n\t\t\t// do nothing\n\t\tcase TCInt64:\n\t\t\traw = fmt.Sprintf(\"___CAST_BOOL_TO_INT64(%s)\", raw)\n\t\tdefault:\n\t\t\treturn NullExpr, newExprErrorf(nil, true, \"Invalid cast from %s to %s\", ex.ValueType, target)\n\t\t}\n\n\tcase TCString:\n\t\tswitch target.Code {\n\t\tcase TCString:\n\t\t\t// do nothing\n\t\tcase TCBool:\n\t\t\traw = fmt.Sprintf(\"___CAST_STRING_TO_BOOL(%s)\", raw)\n\t\tcase TCInt64:\n\t\t\traw = fmt.Sprintf(\"___CAST_STRING_TO_INT64(%s)\", raw)\n\t\tcase TCFloat64:\n\t\t\traw = fmt.Sprintf(\"___CAST_STRING_TO_FLOAT64(%s)\", raw)\n\t\tcase TCDate:\n\t\t\traw = fmt.Sprintf(\"___CAST_STRING_TO_DATE(%s)\", raw)\n\t\tcase TCTimestamp:\n\t\t\traw = fmt.Sprintf(\"___CAST_STRING_TO_TIMESTAMP(%s)\", raw)\n\t\tdefault:\n\t\t\treturn NullExpr, newExprErrorf(nil, true, \"Invalid cast from %s to %s\", ex.ValueType, target)\n\t\t}\n\n\tcase TCBytes:\n\t\tswitch target.Code {\n\t\tcase TCBytes:\n\t\t\t// do nothing\n\t\tcase TCString:\n\t\t\t// do nothing?\n\t\tdefault:\n\t\t\treturn NullExpr, newExprErrorf(nil, true, \"Invalid cast from %s to %s\", ex.ValueType, target)\n\t\t}\n\n\tcase TCDate:\n\t\tswitch target.Code {\n\t\tcase TCString:\n\t\t\traw = fmt.Sprintf(\"___CAST_DATE_TO_STRING(%s)\", raw)\n\t\tcase TCDate:\n\t\t\t// do nothing\n\t\tcase TCTimestamp:\n\t\t\traw = fmt.Sprintf(\"___CAST_DATE_TO_TIMESTAMP(%s)\", raw)\n\t\tdefault:\n\t\t\treturn NullExpr, newExprErrorf(nil, true, \"Invalid cast from %s to %s\", ex.ValueType, target)\n\t\t}\n\n\tcase TCTimestamp:\n\t\tswitch target.Code {\n\t\tcase TCString:\n\t\t\traw = fmt.Sprintf(\"___CAST_TIMESTAMP_TO_STRING(%s)\", raw)\n\t\tcase TCDate:\n\t\t\traw = fmt.Sprintf(\"___CAST_TIMESTAMP_TO_DATE(%s)\", raw)\n\t\tcase TCTimestamp:\n\t\t\t// do nothing\n\t\tdefault:\n\t\t\treturn NullExpr, newExprErrorf(nil, true, \"Invalid cast from %s to %s\", ex.ValueType, target)\n\t\t}\n\n\tcase TCArray:\n\t\tif !compareValueType(ex.ValueType, target) {\n\t\t\tif ex.ValueType.Code == TCArray && target.Code == TCArray {\n\t\t\t\tmsg := \"Casting between arrays with incompatible element types is not supported: Invalid cast from %s to %s\"\n\t\t\t\treturn NullExpr, newExprErrorf(nil, true, msg, ex.ValueType, target)\n\t\t\t}\n\n\t\t\treturn NullExpr, newExprErrorf(nil, true, \"Invalid cast from %s to %s\", ex.ValueType, target)\n\t\t}\n\tcase TCStruct:\n\t\tif !compareValueType(ex.ValueType, target) {\n\t\t\treturn NullExpr, newExprErrorf(nil, true, \"Invalid cast from %s to %s\", ex.ValueType, target)\n\t\t}\n\t\treturn NullExpr, newExprErrorf(nil, false, \"struct is not supported\")\n\t}\n\n\treturn Expr{\n\t\tValueType: target,\n\t\tRaw:       raw,\n\t\tArgs:      ex.Args,\n\t}, nil\n}\n\nfunc isComparable(a, b ValueType) bool {\n\tt1 := a.Code\n\tt2 := b.Code\n\tswitch t1 {\n\tcase TCInt64:\n\t\tif t2 == TCInt64 || t2 == TCFloat64 {\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\tcase TCFloat64:\n\t\tif t2 == TCInt64 || t2 == TCFloat64 {\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\tcase TCString:\n\t\tif t2 == TCString {\n\t\t\treturn true\n\t\t}\n\t\tif t2 == TCDate || t2 == TCTimestamp {\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\tcase TCDate:\n\t\tif t2 == TCDate {\n\t\t\treturn true\n\t\t}\n\t\tif t2 == TCString || t2 == TCTimestamp {\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\tcase TCTimestamp:\n\t\tif t2 == TCTimestamp {\n\t\t\treturn true\n\t\t}\n\t\tif t2 == TCDate || t2 == TCTimestamp {\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\n\t// TODO: false by default\n\treturn true\n}\n\nfunc isCoerceableTo(a, b ValueType) bool {\n\tt1 := a.Code\n\tt2 := b.Code\n\tswitch t1 {\n\tcase TCInt64:\n\t\treturn t2 == TCFloat64\n\tcase TCString:\n\t\treturn t2 == TCDate || t2 == TCTimestamp\n\t}\n\n\treturn false\n}\n\nfunc isCastableTo(a, b ValueType) bool {\n\tt1 := a.Code\n\tt2 := b.Code\n\t// See: https://cloud.google.com/spanner/docs/functions-and-operators#casting\n\tswitch t1 {\n\tcase TCInt64:\n\t\treturn t2 == TCString || t2 == TCFloat64 || t2 == TCBool\n\tcase TCFloat64:\n\t\treturn t2 == TCString || t2 == TCInt64\n\tcase TCBool:\n\t\treturn t2 == TCString || t2 == TCInt64\n\tcase TCString:\n\t\treturn true\n\tcase TCBytes:\n\t\treturn t2 == TCString\n\tcase TCDate:\n\t\treturn t2 == TCString || t2 == TCTimestamp\n\tcase TCTimestamp:\n\t\treturn t2 == TCString || t2 == TCDate\n\t}\n\n\treturn false\n}\n\n// isAsStruct checks if the SelectAs is specifically an AsStruct node\nfunc isAsStruct(as ast.SelectAs) bool {\n\t_, ok := as.(*ast.AsStruct)\n\treturn ok\n}\n"
  },
  {
    "path": "server/query_test.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"testing\"\n\n\tcmp \"github.com/google/go-cmp/cmp\"\n)\n\nfunc newTestQueryBuilder() *QueryBuilder {\n\treturn &QueryBuilder{}\n}\n\nfunc TestQueryBuilder_ExpandParamByPlaceholders(t *testing.T) {\n\tb := newTestQueryBuilder()\n\n\ttable := []struct {\n\t\tv    Value\n\t\tph   string\n\t\targs []interface{}\n\t}{\n\t\t{\n\t\t\tv: Value{\n\t\t\t\tData: []bool{true, false},\n\t\t\t},\n\t\t\tph:   \"JSON_ARRAY((?), (?))\",\n\t\t\targs: []interface{}{true, false},\n\t\t},\n\t\t{\n\t\t\tv: Value{\n\t\t\t\tData: []int64{(100), int64(101)},\n\t\t\t},\n\t\t\tph:   \"JSON_ARRAY((?), (?))\",\n\t\t\targs: []interface{}{int64(100), int64(101)},\n\t\t},\n\t\t{\n\t\t\tv: Value{\n\t\t\t\tData: []float64{float64(1.1), float64(1.2)},\n\t\t\t},\n\t\t\tph:   \"JSON_ARRAY((?), (?))\",\n\t\t\targs: []interface{}{float64(1.1), float64(1.2)},\n\t\t},\n\t\t{\n\t\t\tv: Value{\n\t\t\t\tData: []string{\"aa\", \"bb\", \"cc\"},\n\t\t\t},\n\t\t\tph:   \"JSON_ARRAY((?), (?), (?))\",\n\t\t\targs: []interface{}{\"aa\", \"bb\", \"cc\"},\n\t\t},\n\t\t{\n\t\t\tv: Value{\n\t\t\t\tData: [][]byte{[]byte(\"aa\"), []byte(\"bb\"), []byte(\"cc\")},\n\t\t\t},\n\t\t\tph:   \"JSON_ARRAY((?), (?), (?))\",\n\t\t\targs: []interface{}{[]byte(\"aa\"), []byte(\"bb\"), []byte(\"cc\")},\n\t\t},\n\t}\n\n\tfor _, tc := range table {\n\t\ts, err := b.expandParamByPlaceholders(tc.v)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tif s.Raw != tc.ph {\n\t\t\tt.Errorf(\"expect placeholder %q, but got %q\", tc.ph, s.Raw)\n\t\t}\n\n\t\tif diff := cmp.Diff(tc.args, s.Args); diff != \"\" {\n\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t}\n\n\t}\n}\n"
  },
  {
    "path": "server/server.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tiamv1pb \"cloud.google.com/go/iam/apiv1/iampb\"\n\tlropb \"cloud.google.com/go/longrunning/autogen/longrunningpb\"\n\tadminv1pb \"cloud.google.com/go/spanner/admin/database/apiv1/databasepb\"\n\tspannerpb \"cloud.google.com/go/spanner/apiv1/spannerpb\"\n\t\"github.com/cloudspannerecosystem/memefish\"\n\t\"github.com/cloudspannerecosystem/memefish/ast\"\n\t\"github.com/cloudspannerecosystem/memefish/token\"\n\trpcstatus \"google.golang.org/genproto/googleapis/rpc/status\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n\t\"google.golang.org/protobuf/types/known/structpb\"\n\t\"google.golang.org/protobuf/types/known/timestamppb\"\n)\n\ntype FakeSpannerServer interface {\n\tApplyDDL(ctx context.Context, databaseName string, stmt ast.DDL) error\n\n\tspannerpb.SpannerServer\n\tadminv1pb.DatabaseAdminServer\n\tlropb.OperationsServer\n}\n\nfunc NewFakeServer() FakeSpannerServer {\n\treturn &server{\n\t\tautoCreateDatabase: true,\n\t\tdb:                 make(map[string]*database),\n\t\tsessions:           make(map[string]*session),\n\t}\n}\n\ntype server struct {\n\tautoCreateDatabase bool\n\n\tdbMu sync.RWMutex\n\tdb   map[string]*database\n\n\tsessionMu sync.RWMutex\n\tsessions  map[string]*session\n\n\t// Embed the unimplemented Server structs to avoid having to update the server \n\t// implementation whenever there is an addition to any of the three interfaces.\n\tspannerpb.UnimplementedSpannerServer\n\tadminv1pb.UnimplementedDatabaseAdminServer\n\tlropb.UnimplementedOperationsServer\n}\n\nfunc (s *server) ApplyDDL(ctx context.Context, databaseName string, stmt ast.DDL) error {\n\tdb, err := s.getOrCreateDatabase(databaseName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn db.ApplyDDL(ctx, stmt)\n}\n\n// CreateDatabase implements adminv1pb.DatabaseAdminServer.\nfunc (s *server) CreateDatabase(ctx context.Context, req *adminv1pb.CreateDatabaseRequest) (*lropb.Operation, error) {\n\tddl, err := (&memefish.Parser{\n\t\tLexer: &memefish.Lexer{\n\t\t\tFile: &token.File{FilePath: \"\", Buffer: req.GetCreateStatement()},\n\t\t},\n\t}).ParseDDL()\n\tif err != nil {\n\t\treturn nil, status.Errorf(codes.InvalidArgument, \"Errors parsing Spanner DDL statement: %s : %s\", req.GetCreateStatement(), err)\n\t}\n\tcreateStmt, ok := ddl.(*ast.CreateDatabase)\n\tif !ok {\n\t\treturn nil, status.Error(codes.InvalidArgument, \"Create statement is not CREATE DATABASE\")\n\t}\n\n\tvar stmts []ast.DDL\n\tfor _, s := range req.GetExtraStatements() {\n\t\tstmt, err := (&memefish.Parser{\n\t\t\tLexer: &memefish.Lexer{\n\t\t\t\tFile: &token.File{FilePath: \"\", Buffer: s},\n\t\t\t},\n\t\t}).ParseDDL()\n\t\tif err != nil {\n\t\t\treturn nil, status.Errorf(codes.InvalidArgument, \"Errors parsing Spanner DDL statement: %s : %s\", s, err)\n\t\t}\n\t\tstmts = append(stmts, stmt)\n\t}\n\n\tdatabaseName := strings.Join([]string{req.GetParent(), \"databases\", createStmt.Name.Name}, \"/\")\n\tif _, err := s.createDatabase(databaseName); err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, ddl := range stmts {\n\t\tif err := s.ApplyDDL(ctx, databaseName, ddl); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// TODO: save operation\n\tresp, _ := anypb.New(&adminv1pb.Database{\n\t\tName:  databaseName,\n\t\tState: adminv1pb.Database_READY,\n\t})\n\top := &lropb.Operation{\n\t\tName:     fmt.Sprintf(\"%s/operations/_auto_%d\", databaseName, time.Now().UnixNano()/1000),\n\t\tMetadata: resp,\n\t\tDone:     true,\n\t\tResult: &lropb.Operation_Response{\n\t\t\tResponse: resp,\n\t\t},\n\t}\n\treturn op, nil\n}\n\n// Gets the state of a Cloud Spanner database.\nfunc (s *server) GetDatabase(ctx context.Context, req *adminv1pb.GetDatabaseRequest) (*adminv1pb.Database, error) {\n\t_, err := s.getDatabase(req.Name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &adminv1pb.Database{\n\t\tName:  req.Name,\n\t\tState: adminv1pb.Database_READY,\n\t}, nil\n}\n\n// Lists Cloud Spanner databases.\nfunc (s *server) ListDatabases(ctx context.Context, req *adminv1pb.ListDatabasesRequest) (*adminv1pb.ListDatabasesResponse, error) {\n\ts.dbMu.RLock()\n\tdefer s.dbMu.RUnlock()\n\n\t// TODO: filter by parent\n\n\tdbs := make([]*adminv1pb.Database, 0, len(s.db))\n\tfor name := range s.db {\n\t\tdbs = append(dbs, &adminv1pb.Database{\n\t\t\tName:  name,\n\t\t\tState: adminv1pb.Database_READY,\n\t\t})\n\t}\n\n\t// TODO: respect page token\n\n\treturn &adminv1pb.ListDatabasesResponse{Databases: dbs}, nil\n}\n\nfunc (s *server) UpdateDatabaseDdl(ctx context.Context, req *adminv1pb.UpdateDatabaseDdlRequest) (*lropb.Operation, error) {\n\tvar stmts []ast.DDL\n\tfor _, s := range req.Statements {\n\t\tstmt, err := (&memefish.Parser{\n\t\t\tLexer: &memefish.Lexer{\n\t\t\t\tFile: &token.File{FilePath: \"\", Buffer: s},\n\t\t\t},\n\t\t}).ParseDDL()\n\t\tif err != nil {\n\t\t\treturn nil, status.Errorf(codes.InvalidArgument, \"invalid ddl %q: %v\", s, err)\n\t\t}\n\t\tstmts = append(stmts, stmt)\n\t}\n\n\tfor _, ddl := range stmts {\n\t\t_ = s.ApplyDDL(ctx, req.Database, ddl)\n\t}\n\n\top := &lropb.Operation{\n\t\tName: \"TODO:xxx\",\n\t}\n\treturn op, nil\n}\n\nfunc (s *server) UpdateDatabase(context.Context, *adminv1pb.UpdateDatabaseRequest) (*lropb.Operation, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: UpdateDatabase\")\n}\n\n// DropDatabase implements adminv1pb.DatabaseAdminServer.\nfunc (s *server) DropDatabase(ctx context.Context, req *adminv1pb.DropDatabaseRequest) (*emptypb.Empty, error) {\n\tif err := s.dropDatabase(req.GetDatabase()); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &emptypb.Empty{}, nil\n}\n\n// Returns the schema of a Cloud Spanner database as a list of formatted\n// DDL statements. This method does not show pending schema updates, those may\n// be queried using the [Operations][google.longrunning.Operations] API.\nfunc (s *server) GetDatabaseDdl(context.Context, *adminv1pb.GetDatabaseDdlRequest) (*adminv1pb.GetDatabaseDdlResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: ExecuteSql\")\n}\n\nfunc (s *server) SetIamPolicy(context.Context, *iamv1pb.SetIamPolicyRequest) (*iamv1pb.Policy, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: SetIamPolixy\")\n}\n\nfunc (s *server) GetIamPolicy(context.Context, *iamv1pb.GetIamPolicyRequest) (*iamv1pb.Policy, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: GetIamPolicy\")\n}\n\nfunc (s *server) TestIamPermissions(context.Context, *iamv1pb.TestIamPermissionsRequest) (*iamv1pb.TestIamPermissionsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: TestIamPermissions\")\n}\n\nfunc (s *server) ListOperations(ctx context.Context, req *lropb.ListOperationsRequest) (*lropb.ListOperationsResponse, error) {\n\t// TODO\n\treturn &lropb.ListOperationsResponse{}, nil\n}\n\nfunc (s *server) GetOperation(ctx context.Context, req *lropb.GetOperationRequest) (*lropb.Operation, error) {\n\tname := \"TODO:xxx\"\n\tif req.Name != name {\n\t\treturn nil, status.Errorf(codes.NotFound, \"Operation not found: %s\", name)\n\t}\n\n\tany, _ := anypb.New(&emptypb.Empty{})\n\top := &lropb.Operation{\n\t\tName:     \"TODO:xxx\",\n\t\tMetadata: any,\n\t\tDone:     true,\n\t\tResult: &lropb.Operation_Response{\n\t\t\tResponse: any,\n\t\t},\n\t}\n\treturn op, nil\n}\n\nfunc (s *server) DeleteOperation(ctx context.Context, req *lropb.DeleteOperationRequest) (*emptypb.Empty, error) {\n\treturn &emptypb.Empty{}, nil\n}\n\nfunc (s *server) CancelOperation(ctx context.Context, req *lropb.CancelOperationRequest) (*emptypb.Empty, error) {\n\treturn &emptypb.Empty{}, nil\n}\n\nfunc (s *server) WaitOperation(ctx context.Context, req *lropb.WaitOperationRequest) (*lropb.Operation, error) {\n\tname := \"TODO:xxx\"\n\tif req.Name != name {\n\t\treturn nil, status.Errorf(codes.NotFound, \"Operation not found: %s\", name)\n\t}\n\tany, _ := anypb.New(&emptypb.Empty{})\n\top := &lropb.Operation{\n\t\tName:     \"TODO:xxx\",\n\t\tMetadata: any,\n\t\tDone:     true,\n\t\tResult: &lropb.Operation_Response{\n\t\t\tResponse: any,\n\t\t},\n\t}\n\treturn op, nil\n}\n\nfunc parseDatabaseName(fullDatabaseName string) ([]string, bool) {\n\tparts := strings.Split(fullDatabaseName, \"/\")\n\tif len(parts) != 6 {\n\t\treturn nil, false\n\t}\n\tif parts[0] != \"projects\" {\n\t\treturn nil, false\n\t}\n\tif parts[2] != \"instances\" {\n\t\treturn nil, false\n\t}\n\tif parts[4] != \"databases\" {\n\t\treturn nil, false\n\t}\n\tif parts[1] == \"\" || parts[3] == \"\" || parts[5] == \"\" {\n\t\treturn nil, false\n\t}\n\treturn []string{parts[1], parts[3], parts[5]}, true\n}\n\nfunc (s *server) getSession(name string) (*session, error) {\n\ts.sessionMu.RLock()\n\tdefer s.sessionMu.RUnlock()\n\n\tif !validateSessionName(name) {\n\t\treturn nil, status.Errorf(codes.InvalidArgument, \"Invalid BeginTransaction request\")\n\t}\n\tsession, ok := s.sessions[name]\n\tif !ok {\n\t\treturn nil, newSpannerSessionNotFoundError(name)\n\t}\n\treturn session, nil\n}\n\nfunc (s *server) createSession(db *database, dbName string) (*session, error) {\n\ts.sessionMu.Lock()\n\tdefer s.sessionMu.Unlock()\n\n\tfor i := 0; i < 3; i++ {\n\t\tsession := newSession(db, dbName)\n\t\tif _, ok := s.sessions[session.Name()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\ts.sessions[session.Name()] = session\n\t\treturn session, nil\n\t}\n\n\treturn nil, status.Errorf(codes.Unknown, \"create session failed\")\n}\n\nfunc (s *server) createDatabase(name string) (*database, error) {\n\tif _, ok := parseDatabaseName(name); !ok {\n\t\treturn nil, status.Errorf(codes.InvalidArgument, \"Invalid CreateDatabase request.\")\n\t}\n\n\t// read lock to check database exists\n\ts.dbMu.RLock()\n\t_, ok := s.db[name]\n\ts.dbMu.RUnlock()\n\tif ok {\n\t\treturn nil, status.Errorf(codes.AlreadyExists, \"Database already exists: %s\", name)\n\t}\n\n\t// write lock\n\ts.dbMu.Lock()\n\tdefer s.dbMu.Unlock()\n\n\t// re-check after lock\n\tif _, ok := s.db[name]; ok {\n\t\treturn nil, status.Errorf(codes.AlreadyExists, \"Database already exists: %s\", name)\n\t}\n\n\tdb := newDatabase()\n\ts.db[name] = db\n\n\treturn db, nil\n}\n\nfunc (s *server) getOrCreateDatabase(name string) (*database, error) {\n\ts.dbMu.RLock()\n\tdb, ok := s.db[name]\n\ts.dbMu.RUnlock()\n\tif !ok {\n\t\tif !s.autoCreateDatabase {\n\t\t\treturn nil, newSpannerDatabaseNotFoundError(name)\n\t\t}\n\n\t\tvar err error\n\t\tdb, err = s.createDatabase(name)\n\t\tif err != nil {\n\t\t\tst, ok := status.FromError(err)\n\t\t\tif ok && st.Code() == codes.AlreadyExists {\n\t\t\t\treturn s.getOrCreateDatabase(name)\n\t\t\t}\n\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn db, nil\n}\n\nfunc (s *server) getDatabase(name string) (*database, error) {\n\ts.dbMu.RLock()\n\tdb, ok := s.db[name]\n\ts.dbMu.RUnlock()\n\tif !ok {\n\t\treturn nil, newSpannerDatabaseNotFoundError(name)\n\t}\n\n\treturn db, nil\n}\n\nfunc (s *server) dropDatabase(name string) error {\n\tif _, ok := parseDatabaseName(name); !ok {\n\t\treturn status.Error(codes.InvalidArgument, \"Invalid DropDatabase request.\")\n\t}\n\n\ts.dbMu.RLock()\n\tdb, found := s.db[name]\n\ts.dbMu.RUnlock()\n\tif !found {\n\t\treturn nil\n\t}\n\n\tif err := db.Close(); err != nil {\n\t\treturn status.Errorf(codes.Unknown, \"Failed to close the database: %s\", err)\n\t}\n\n\ts.dbMu.Lock()\n\tdelete(s.db, name)\n\ts.dbMu.Unlock()\n\n\treturn nil\n}\n\nfunc (s *server) CreateSession(ctx context.Context, req *spannerpb.CreateSessionRequest) (*spannerpb.Session, error) {\n\t_, ok := parseDatabaseName(req.Database)\n\tif !ok {\n\t\treturn nil, status.Errorf(codes.InvalidArgument, \"Invalid CreateSession request\")\n\t}\n\n\tdb, err := s.getOrCreateDatabase(req.Database)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsession, err := s.createSession(db, req.Database)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn session.Proto(), nil\n}\n\nfunc (s *server) BatchCreateSessions(ctx context.Context, req *spannerpb.BatchCreateSessionsRequest) (*spannerpb.BatchCreateSessionsResponse, error) {\n\t_, ok := parseDatabaseName(req.Database)\n\tif !ok {\n\t\treturn nil, status.Errorf(codes.InvalidArgument, \"Invalid BatchCreateSessions request\")\n\t}\n\n\tdb, err := s.getOrCreateDatabase(req.Database)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessions := make([]*spannerpb.Session, req.SessionCount)\n\tfor i := 0; i < int(req.SessionCount); i++ {\n\t\ts, err := s.createSession(db, req.Database)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tsessions[i] = s.Proto()\n\t}\n\n\treturn &spannerpb.BatchCreateSessionsResponse{\n\t\tSession: sessions,\n\t}, nil\n}\n\nfunc (s *server) GetSession(ctx context.Context, req *spannerpb.GetSessionRequest) (*spannerpb.Session, error) {\n\tsession, err := s.getSession(req.Name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn session.Proto(), nil\n}\n\nfunc (s *server) ListSessions(ctx context.Context, req *spannerpb.ListSessionsRequest) (*spannerpb.ListSessionsResponse, error) {\n\ts.sessionMu.RLock()\n\tdefer s.sessionMu.RUnlock()\n\n\t// TODO: respect page size\n\n\tprefix := req.Database + \"/\"\n\tvar sessions []*spannerpb.Session\n\tfor name, session := range s.sessions {\n\t\tif !strings.HasPrefix(name, prefix) {\n\t\t\tcontinue\n\t\t}\n\n\t\tsessions = append(sessions, session.Proto())\n\t}\n\n\treturn &spannerpb.ListSessionsResponse{\n\t\tSessions: sessions,\n\t}, nil\n}\n\nfunc (s *server) DeleteSession(ctx context.Context, req *spannerpb.DeleteSessionRequest) (*emptypb.Empty, error) {\n\tsession, err := s.getSession(req.Name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ts.sessionMu.Lock()\n\tdefer s.sessionMu.Unlock()\n\tdelete(s.sessions, session.Name())\n\n\treturn &emptypb.Empty{}, nil\n}\n\nfunc (s *server) ExecuteSql(ctx context.Context, req *spannerpb.ExecuteSqlRequest) (*spannerpb.ResultSet, error) {\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\n\tsession, err := s.getSession(req.Session)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx, txCreated, err := session.GetTransactionBySelector(req.GetTransaction())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif tx.SingleUse() {\n\t\ttx.Done(TransactionRollbacked)\n\t\tmsg := \"DML statements may not be performed in single-use transactions, to avoid replay.\"\n\t\treturn nil, status.Errorf(codes.InvalidArgument, msg)\n\t}\n\n\tcheckAvailability := func() error {\n\t\tswitch tx.Status() {\n\t\tcase TransactionInvalidated:\n\t\t\treturn status.Errorf(codes.FailedPrecondition, \"This transaction has been invalidated by a later transaction in the same session.\")\n\t\tcase TransactionCommited, TransactionRollbacked:\n\t\t\treturn status.Errorf(codes.FailedPrecondition, \"Cannot start a read or query within a transaction after Commit() or Rollback() has been called.\")\n\t\tcase TransactionAborted:\n\t\t\treturn status.Errorf(codes.Aborted, \"transaction aborted\")\n\t\t}\n\t\treturn status.Errorf(codes.Unknown, \"unknown status\")\n\t}\n\n\trollbackCreatedTx := func() {\n\t\tif txCreated {\n\t\t\ttx.Done(TransactionAborted)\n\t\t}\n\t}\n\n\tif !tx.Available() {\n\t\treturn nil, checkAvailability()\n\t}\n\n\tif !tx.ReadWrite() {\n\t\tmsg := \"DML statements can only be performed in a read-write transaction.\"\n\t\treturn nil, status.Errorf(codes.FailedPrecondition, msg)\n\t}\n\n\tif req.Sql == \"\" {\n\t\treturn nil, status.Error(codes.InvalidArgument, \"Invalid ExecuteSql request.\")\n\t}\n\n\tresult, err := s.executeDML(ctx, session, tx, &spannerpb.ExecuteBatchDmlRequest_Statement{\n\t\tSql:        req.GetSql(),\n\t\tParams:     req.GetParams(),\n\t\tParamTypes: req.GetParamTypes(),\n\t})\n\tif err != nil {\n\t\trollbackCreatedTx()\n\t\tif !tx.Available() {\n\t\t\treturn nil, checkAvailability()\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tif txCreated {\n\t\tresult.Metadata = &spannerpb.ResultSetMetadata{\n\t\t\tTransaction: tx.Proto(),\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\nfunc (s *server) ExecuteStreamingSql(req *spannerpb.ExecuteSqlRequest, stream spannerpb.Spanner_ExecuteStreamingSqlServer) error {\n\treceivedAt := time.Now().UTC()\n\tctx, cancel := context.WithCancel(stream.Context())\n\tdefer cancel()\n\n\tsession, err := s.getSession(req.Session)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttx, txCreated, err := session.GetTransactionBySelector(req.GetTransaction())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcheckAvailability := func() error {\n\t\tswitch tx.Status() {\n\t\tcase TransactionInvalidated:\n\t\t\treturn status.Errorf(codes.FailedPrecondition, \"This transaction has been invalidated by a later transaction in the same session.\")\n\t\tcase TransactionCommited, TransactionRollbacked:\n\t\t\treturn status.Errorf(codes.FailedPrecondition, \"Cannot start a read or query within a transaction after Commit() or Rollback() has been called.\")\n\t\tcase TransactionAborted:\n\t\t\treturn status.Errorf(codes.Aborted, \"transaction aborted\")\n\t\t}\n\t\treturn status.Errorf(codes.Unknown, \"unknown status\")\n\t}\n\n\trollbackCreatedTx := func() {\n\t\tif txCreated {\n\t\t\ttx.Done(TransactionAborted)\n\t\t}\n\t}\n\n\tif !tx.Available() {\n\t\treturn checkAvailability()\n\t}\n\n\tif tx.SingleUse() {\n\t\tgo func(ctx context.Context) {\n\t\t\t// make sure to call tx.Done() after the request finished\n\t\t\t<-ctx.Done()\n\t\t\ttx.Done(TransactionCommited)\n\t\t}(stream.Context())\n\t}\n\n\tif req.Sql == \"\" {\n\t\treturn status.Error(codes.InvalidArgument, \"Invalid ExecuteStreamingSql request.\")\n\t}\n\n\tstmt, err := (&memefish.Parser{\n\t\tLexer: &memefish.Lexer{\n\t\t\tFile: &token.File{FilePath: \"\", Buffer: req.Sql},\n\t\t},\n\t}).ParseQuery()\n\tif err != nil {\n\t\treturn status.Errorf(codes.InvalidArgument, \"Syntax error: %q: %v\", req.Sql, err)\n\t}\n\n\tfields := req.GetParams().GetFields()\n\tparamTypes := req.ParamTypes\n\tparams := make(map[string]Value, len(fields))\n\tdefaultType := &spannerpb.Type{Code: spannerpb.TypeCode_INT64}\n\tfor key, val := range fields {\n\t\ttyp := defaultType\n\t\tif paramTypes != nil {\n\t\t\tif t, ok := paramTypes[key]; ok {\n\t\t\t\ttyp = t\n\t\t\t}\n\t\t}\n\n\t\tv, err := makeValueFromSpannerValue(key, val, typ)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tparams[key] = v\n\t}\n\n\titer, err := session.database.Query(ctx, tx, stmt, params)\n\tif err != nil {\n\t\trollbackCreatedTx()\n\t\tif !tx.Available() {\n\t\t\treturn checkAvailability()\n\t\t}\n\t\treturn err\n\t}\n\n\tstats := queryStats{\n\t\tMode:       req.QueryMode,\n\t\tReceivedAt: receivedAt,\n\t\tQueryText:  req.Sql,\n\t}\n\tif err := sendResult(stream, tx, iter, txCreated, stats); err != nil {\n\t\tif !tx.Available() {\n\t\t\treturn checkAvailability()\n\t\t}\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (s *server) ExecuteBatchDml(ctx context.Context, req *spannerpb.ExecuteBatchDmlRequest) (*spannerpb.ExecuteBatchDmlResponse, error) {\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\n\tsession, err := s.getSession(req.Session)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx, txCreated, err := session.GetTransactionBySelector(req.GetTransaction())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif tx.SingleUse() {\n\t\ttx.Done(TransactionRollbacked)\n\t\tmsg := \"DML statements may not be performed in single-use transactions, to avoid replay.\"\n\t\treturn nil, status.Errorf(codes.InvalidArgument, msg)\n\t}\n\n\tcheckAvailability := func() error {\n\t\tswitch tx.Status() {\n\t\tcase TransactionInvalidated:\n\t\t\treturn status.Errorf(codes.FailedPrecondition, \"This transaction has been invalidated by a later transaction in the same session.\")\n\t\tcase TransactionCommited, TransactionRollbacked:\n\t\t\treturn status.Errorf(codes.FailedPrecondition, \"Cannot start a read or query within a transaction after Commit() or Rollback() has been called.\")\n\t\tcase TransactionAborted:\n\t\t\treturn status.Errorf(codes.Aborted, \"transaction aborted\")\n\t\t}\n\t\treturn status.Errorf(codes.Unknown, \"unknown status\")\n\t}\n\n\tif !tx.Available() {\n\t\treturn nil, checkAvailability()\n\t}\n\n\tif !tx.ReadWrite() {\n\t\tmsg := \"DML statements can only be performed in a read-write transaction.\"\n\t\treturn nil, status.Errorf(codes.FailedPrecondition, msg)\n\t}\n\n\tif len(req.Statements) == 0 {\n\t\tmsg := \"No statements in batch DML request.\"\n\t\treturn nil, status.Errorf(codes.InvalidArgument, msg)\n\t}\n\n\tvar resultSets []*spannerpb.ResultSet\n\tvar resultStatus rpcstatus.Status\n\tfor i, stmt := range req.Statements {\n\t\tresult, err := s.executeDML(ctx, session, tx, stmt)\n\t\tif err != nil {\n\t\t\t// TODO: confirm to require rollback transaction in partial success\n\t\t\tif !tx.Available() {\n\t\t\t\treturn nil, checkAvailability()\n\t\t\t}\n\t\t\tst := status.Convert(err)\n\t\t\tresultStatus.Code = int32(st.Code())\n\t\t\tresultStatus.Message = fmt.Sprintf(\"Statement %d: %s\", i, st.Message())\n\t\t\tbreak\n\t\t}\n\n\t\tresultSets = append(resultSets, result)\n\t}\n\n\tif txCreated {\n\t\tif len(resultSets) > 0 {\n\t\t\tresultSets[0].Metadata = &spannerpb.ResultSetMetadata{\n\t\t\t\tTransaction: tx.Proto(),\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &spannerpb.ExecuteBatchDmlResponse{\n\t\tResultSets: resultSets,\n\t\tStatus:     &resultStatus,\n\t}, nil\n}\n\nfunc (s *server) executeDML(ctx context.Context, session *session, tx *transaction, stmt *spannerpb.ExecuteBatchDmlRequest_Statement) (*spannerpb.ResultSet, error) {\n\tdml, err := (&memefish.Parser{\n\t\tLexer: &memefish.Lexer{\n\t\t\tFile: &token.File{FilePath: \"\", Buffer: stmt.Sql},\n\t\t},\n\t}).ParseDML()\n\tif err != nil {\n\t\treturn nil, status.Errorf(codes.InvalidArgument, \"%q is not valid DML: %v\", stmt.Sql, err)\n\t}\n\n\tfields := stmt.GetParams().GetFields()\n\tparamTypes := stmt.ParamTypes\n\tparams := make(map[string]Value, len(fields))\n\tdefaultType := &spannerpb.Type{Code: spannerpb.TypeCode_INT64}\n\tfor key, val := range fields {\n\t\ttyp := defaultType\n\t\tif paramTypes != nil {\n\t\t\tif t, ok := paramTypes[key]; ok {\n\t\t\t\ttyp = t\n\t\t\t}\n\t\t}\n\n\t\tv, err := makeValueFromSpannerValue(key, val, typ)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tparams[key] = v\n\t}\n\n\tcount, err := session.database.Execute(ctx, tx, dml, params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &spannerpb.ResultSet{\n\t\tStats: &spannerpb.ResultSetStats{\n\t\t\tRowCount: &spannerpb.ResultSetStats_RowCountExact{\n\t\t\t\tRowCountExact: count,\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nfunc (s *server) Read(ctx context.Context, req *spannerpb.ReadRequest) (*spannerpb.ResultSet, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: Read\")\n}\n\nfunc (s *server) StreamingRead(req *spannerpb.ReadRequest, stream spannerpb.Spanner_StreamingReadServer) error {\n\treceivedAt := time.Now().UTC()\n\tctx, cancel := context.WithCancel(stream.Context())\n\tdefer cancel()\n\n\tsession, err := s.getSession(req.Session)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttx, txCreated, err := session.GetTransactionBySelector(req.GetTransaction())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcheckAvailability := func() error {\n\t\tswitch tx.Status() {\n\t\tcase TransactionInvalidated:\n\t\t\treturn status.Errorf(codes.FailedPrecondition, \"This transaction has been invalidated by a later transaction in the same session.\")\n\t\tcase TransactionCommited, TransactionRollbacked:\n\t\t\treturn status.Errorf(codes.FailedPrecondition, \"Cannot start a read or query within a transaction after Commit() or Rollback() has been called.\")\n\t\tcase TransactionAborted:\n\t\t\treturn status.Errorf(codes.Aborted, \"transaction aborted\")\n\t\t}\n\t\treturn status.Errorf(codes.Unknown, \"unknown status\")\n\t}\n\n\trollbackCreatedTx := func() {\n\t\tif txCreated {\n\t\t\ttx.Done(TransactionAborted)\n\t\t}\n\t}\n\n\tif !tx.Available() {\n\t\treturn checkAvailability()\n\t}\n\n\tif tx.SingleUse() {\n\t\tgo func(ctx context.Context) {\n\t\t\t// make sure to call tx.Done() after the request finished\n\t\t\t<-ctx.Done()\n\t\t\ttx.Done(TransactionCommited)\n\t\t}(stream.Context())\n\t}\n\n\titer, err := session.database.Read(ctx, tx, req.Table, req.Index, req.Columns, makeKeySet(req.KeySet), req.Limit)\n\tif err != nil {\n\t\trollbackCreatedTx()\n\t\tif !tx.Available() {\n\t\t\treturn checkAvailability()\n\t\t}\n\t\treturn err\n\t}\n\n\tstats := queryStats{\n\t\tMode:       spannerpb.ExecuteSqlRequest_NORMAL,\n\t\tReceivedAt: receivedAt,\n\t\tQueryText:  \"\",\n\t}\n\tif err := sendResult(stream, tx, iter, txCreated, stats); err != nil {\n\t\tif !tx.Available() {\n\t\t\treturn checkAvailability()\n\t\t}\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc sendResult(stream spannerpb.Spanner_StreamingReadServer, tx *transaction, iter RowIterator, returnTx bool, qs queryStats) error {\n\t// Create metadata about columns\n\tfields := make([]*spannerpb.StructType_Field, len(iter.ResultSet()))\n\tfor i, item := range iter.ResultSet() {\n\t\tfields[i] = &spannerpb.StructType_Field{\n\t\t\tName: item.Name,\n\t\t\tType: makeSpannerTypeFromValueType(item.ValueType),\n\t\t}\n\t}\n\n\tvar txProto *spannerpb.Transaction\n\tif returnTx {\n\t\ttxProto = tx.Proto()\n\t}\n\n\tmetadata := &spannerpb.ResultSetMetadata{\n\t\tRowType:     &spannerpb.StructType{Fields: fields},\n\t\tTransaction: txProto,\n\t}\n\n\tvar rowCount int64\n\tvalues := make([]*structpb.Value, 0, 100)\n\terr := iter.Do(func(row []interface{}) error {\n\t\trowCount++\n\t\tfor _, x := range row {\n\t\t\tv, err := spannerValueFromValue(x)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tvalues = append(values, v)\n\t\t}\n\n\t\tif len(values) > 100 {\n\t\t\tif err := stream.Send(&spannerpb.PartialResultSet{\n\t\t\t\tMetadata: metadata,\n\t\t\t\tValues:   values,\n\t\t\t}); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// From documents:\n\t\t\t// Metadata about the result set, such as row type information.\n\t\t\t// Only present in the first response.\n\t\t\tmetadata = nil\n\n\t\t\t// // Stats is only present once in the last response.\n\t\t\t// // But set the first response for now.\n\t\t\t// stats = nil\n\n\t\t\tvalues = values[:0]\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tqs.RowCount = rowCount\n\tstats := &spannerpb.ResultSetStats{\n\t\tQueryStats: createQueryStats(qs),\n\t}\n\n\tif err := stream.Send(&spannerpb.PartialResultSet{\n\t\tMetadata: metadata,\n\t\tValues:   values,\n\t\tStats:    stats,\n\t}); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\ntype queryStats struct {\n\tMode       spannerpb.ExecuteSqlRequest_QueryMode\n\tReceivedAt time.Time\n\tQueryText  string\n\tRowCount   int64\n}\n\nfunc toMillisecondString(d time.Duration) string {\n\treturn fmt.Sprintf(\"%d.%d msecs\", d/time.Millisecond, (d%time.Millisecond)/time.Microsecond)\n}\n\nfunc createQueryStats(stats queryStats) *structpb.Struct {\n\telapsedTime := time.Since(stats.ReceivedAt)\n\treturn &structpb.Struct{\n\t\tFields: map[string]*structpb.Value{\n\t\t\t\"cpu_time\": {\n\t\t\t\tKind: &structpb.Value_StringValue{\n\t\t\t\t\tStringValue: toMillisecondString(elapsedTime),\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"remote_server_calls\": {\n\t\t\t\tKind: &structpb.Value_StringValue{\n\t\t\t\t\tStringValue: \"0/0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"bytes_returned\": {\n\t\t\t\tKind: &structpb.Value_StringValue{\n\t\t\t\t\tStringValue: \"8\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"query_text\": {\n\t\t\t\tKind: &structpb.Value_StringValue{\n\t\t\t\t\tStringValue: stats.QueryText,\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"query_plan_creation_time\": {\n\t\t\t\tKind: &structpb.Value_StringValue{\n\t\t\t\t\tStringValue: \"0 msecs\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"runtime_creation_time\": {\n\t\t\t\tKind: &structpb.Value_StringValue{\n\t\t\t\t\tStringValue: \"0 msecs\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"deleted_rows_scanned\": {\n\t\t\t\tKind: &structpb.Value_StringValue{\n\t\t\t\t\tStringValue: \"0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"optimizer_version\": {\n\t\t\t\tKind: &structpb.Value_StringValue{\n\t\t\t\t\tStringValue: \"2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"elapsed_time\": {\n\t\t\t\tKind: &structpb.Value_StringValue{\n\t\t\t\t\tStringValue: toMillisecondString(elapsedTime),\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"rows_returned\": {\n\t\t\t\tKind: &structpb.Value_StringValue{\n\t\t\t\t\tStringValue: fmt.Sprintf(\"%d\", stats.RowCount),\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"filesystem_delay_seconds\": {\n\t\t\t\tKind: &structpb.Value_StringValue{\n\t\t\t\t\tStringValue: \"0 msecs\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"rows_scanned\": {\n\t\t\t\tKind: &structpb.Value_StringValue{\n\t\t\t\t\tStringValue: \"0\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc (s *server) BeginTransaction(ctx context.Context, req *spannerpb.BeginTransactionRequest) (*spannerpb.Transaction, error) {\n\tsession, err := s.getSession(req.Session)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx, err := session.BeginTransaction(req.GetOptions())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tx.Proto(), nil\n}\n\nfunc (s *server) Commit(ctx context.Context, req *spannerpb.CommitRequest) (*spannerpb.CommitResponse, error) {\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\n\tsession, err := s.getSession(req.Session)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx, err := session.GetTransactionForCommit(req.GetTransaction())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcheckAvailability := func() (*spannerpb.CommitResponse, error) {\n\t\tswitch tx.Status() {\n\t\tcase TransactionInvalidated:\n\t\t\treturn nil, status.Errorf(codes.FailedPrecondition, \"This transaction has been invalidated by a later transaction in the same session.\")\n\t\tcase TransactionCommited:\n\t\t\treturn &spannerpb.CommitResponse{}, nil\n\t\tcase TransactionRollbacked:\n\t\t\treturn nil, status.Errorf(codes.FailedPrecondition, \"Cannot commit a transaction after Rollback() has been called.\")\n\t\tcase TransactionAborted:\n\t\t\treturn nil, status.Errorf(codes.Aborted, \"transaction aborted\")\n\t\t}\n\t\treturn nil, status.Errorf(codes.Unknown, \"unknown status\")\n\t}\n\n\tif !tx.Available() {\n\t\treturn checkAvailability()\n\t}\n\n\tif !tx.ReadWrite() {\n\t\tvar msg string\n\t\tif tx.SingleUse() {\n\t\t\tmsg = \"Cannot commit a single-use read-only transaction.\"\n\t\t} else {\n\t\t\tmsg = \"Cannot commit a read-only transaction.\"\n\t\t}\n\t\treturn nil, status.Errorf(codes.FailedPrecondition, msg)\n\t}\n\n\terr = func() error {\n\t\tfor _, m := range req.Mutations {\n\t\t\tswitch op := m.Operation.(type) {\n\t\t\tcase *spannerpb.Mutation_Insert:\n\t\t\t\tmut := op.Insert\n\t\t\t\tif err := session.database.Insert(ctx, tx, mut.Table, mut.Columns, mut.Values); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\tcase *spannerpb.Mutation_Update:\n\t\t\t\tmut := op.Update\n\t\t\t\tif err := session.database.Update(ctx, tx, mut.Table, mut.Columns, mut.Values); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\tcase *spannerpb.Mutation_Replace:\n\t\t\t\tmut := op.Replace\n\t\t\t\tif err := session.database.Replace(ctx, tx, mut.Table, mut.Columns, mut.Values); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\tcase *spannerpb.Mutation_InsertOrUpdate:\n\t\t\t\tmut := op.InsertOrUpdate\n\t\t\t\tif err := session.database.InsertOrUpdate(ctx, tx, mut.Table, mut.Columns, mut.Values); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\tcase *spannerpb.Mutation_Delete_:\n\t\t\t\tmut := op.Delete\n\t\t\t\tif err := session.database.Delete(ctx, tx, mut.Table, makeKeySet(mut.KeySet)); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\tdefault:\n\t\t\t\treturn fmt.Errorf(\"unknown mutation operation: %v\", op)\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}()\n\tif err != nil {\n\t\tif !tx.Available() {\n\t\t\treturn checkAvailability()\n\t\t}\n\n\t\ttx.Done(TransactionRollbacked)\n\t\treturn nil, err\n\t}\n\n\tif err := session.database.Commit(tx); err != nil {\n\t\tif !tx.Available() {\n\t\t\treturn checkAvailability()\n\t\t}\n\n\t\ttx.Done(TransactionRollbacked)\n\t\treturn nil, err // TODO\n\t}\n\n\ttx.Done(TransactionCommited)\n\n\t// TODO: more accurate time\n\tcommitTime := time.Now()\n\tcommitTimeProto := timestamppb.New(commitTime)\n\n\treturn &spannerpb.CommitResponse{\n\t\tCommitTimestamp: commitTimeProto,\n\t}, nil\n}\n\nfunc (s *server) Rollback(ctx context.Context, req *spannerpb.RollbackRequest) (*emptypb.Empty, error) {\n\tsession, err := s.getSession(req.Session)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttx, ok := session.GetTransaction(req.TransactionId)\n\tif !ok {\n\t\treturn nil, status.Errorf(codes.InvalidArgument, \"Transaction was started in a different session\")\n\t}\n\n\tif !tx.Available() {\n\t\tswitch tx.Status() {\n\t\tcase TransactionInvalidated:\n\t\t\treturn nil, status.Errorf(codes.FailedPrecondition, \"This transaction has been invalidated by a later transaction in the same session.\")\n\t\tcase TransactionCommited:\n\t\t\treturn nil, status.Errorf(codes.FailedPrecondition, \"Cannot rollback a transaction after Commit() has been called.\")\n\t\tcase TransactionRollbacked:\n\t\t\treturn &emptypb.Empty{}, nil\n\t\tcase TransactionAborted:\n\t\t\treturn nil, status.Errorf(codes.Aborted, \"transaction aborted\")\n\t\t}\n\t}\n\n\tif !tx.ReadWrite() {\n\t\treturn nil, status.Errorf(codes.FailedPrecondition, \"Cannot rollback a read-only transaction.\")\n\t}\n\n\tif err := session.database.Rollback(tx); err != nil {\n\t\treturn nil, err // TODO\n\t}\n\ttx.Done(TransactionRollbacked)\n\n\treturn &emptypb.Empty{}, nil\n}\n\nfunc (s *server) PartitionQuery(ctx context.Context, req *spannerpb.PartitionQueryRequest) (*spannerpb.PartitionResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: PartitionQuery\")\n}\n\nfunc (s *server) PartitionRead(ctx context.Context, req *spannerpb.PartitionReadRequest) (*spannerpb.PartitionResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: PartitionRead\")\n}\n\nfunc (s *server) CreateBackup(ctx context.Context, req *adminv1pb.CreateBackupRequest) (*lropb.Operation, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: CreateBackup\")\n}\n\nfunc (s *server) GetBackup(ctx context.Context, req *adminv1pb.GetBackupRequest) (*adminv1pb.Backup, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: GetBackup\")\n}\n\nfunc (s *server) UpdateBackup(ctx context.Context, req *adminv1pb.UpdateBackupRequest) (*adminv1pb.Backup, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: UpdateBackup\")\n}\n\nfunc (s *server) DeleteBackup(ctx context.Context, req *adminv1pb.DeleteBackupRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: DeleteBackup\")\n}\n\nfunc (s *server) ListBackups(ctx context.Context, req *adminv1pb.ListBackupsRequest) (*adminv1pb.ListBackupsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: ListBackups\")\n}\n\nfunc (s *server) RestoreDatabase(ctx context.Context, req *adminv1pb.RestoreDatabaseRequest) (*lropb.Operation, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: RestoreDatabase\")\n}\n\nfunc (s *server) ListDatabaseOperations(ctx context.Context, req *adminv1pb.ListDatabaseOperationsRequest) (*adminv1pb.ListDatabaseOperationsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: ListDatabaseOperations\")\n}\n\nfunc (s *server) ListBackupOperations(ctx context.Context, req *adminv1pb.ListBackupOperationsRequest) (*adminv1pb.ListBackupOperationsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: ListBackupOperations\")\n}\n\nfunc (s *server) CopyBackup(ctx context.Context, req *adminv1pb.CopyBackupRequest) (*lropb.Operation, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: CopyBackup\")\n}\n\nfunc (s *server) ListDatabaseRoles(ctx context.Context, req *adminv1pb.ListDatabaseRolesRequest) (*adminv1pb.ListDatabaseRolesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: ListDatabaseRoles\")\n}\n\nfunc (*server) BatchWrite(*spannerpb.BatchWriteRequest, spannerpb.Spanner_BatchWriteServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"not implemented yet: BatchWrite\")\n}\n\nfunc (s *server) CreateBackupSchedule(ctx context.Context, req *adminv1pb.CreateBackupScheduleRequest) (*adminv1pb.BackupSchedule, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: CreateBackupSchedule\")\n}\n\nfunc (s *server) GetBackupSchedule(ctx context.Context, req *adminv1pb.GetBackupScheduleRequest) (*adminv1pb.BackupSchedule, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: GetBackupSchedule\")\n}\n\nfunc (s *server) UpdateBackupSchedule(ctx context.Context, req *adminv1pb.UpdateBackupScheduleRequest) (*adminv1pb.BackupSchedule, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: UpdateBackupSchedule\")\n}\n\nfunc (s *server) DeleteBackupSchedule(ctx context.Context, req *adminv1pb.DeleteBackupScheduleRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: DeleteBackupSchedule\")\n}\n\nfunc (s *server) ListBackupSchedules(ctx context.Context, req *adminv1pb.ListBackupSchedulesRequest) (*adminv1pb.ListBackupSchedulesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: ListBackupSchedules\")\n}\n\nfunc (s *server) AddSplitPoints(ctx context.Context, req *adminv1pb.AddSplitPointsRequest) (*adminv1pb.AddSplitPointsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"not implemented yet: AddSplitPoints\")\n}\n"
  },
  {
    "path": "server/server_test.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tcmp \"github.com/google/go-cmp/cmp\"\n\tuuidpkg \"github.com/google/uuid\"\n\tlropb \"google.golang.org/genproto/googleapis/longrunning\"\n\t\"google.golang.org/genproto/googleapis/rpc/errdetails\"\n\tadminv1pb \"google.golang.org/genproto/googleapis/spanner/admin/database/v1\"\n\tspannerpb \"google.golang.org/genproto/googleapis/spanner/v1\"\n\tgrpc \"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/testing/protocmp\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\t\"google.golang.org/protobuf/types/known/structpb\"\n)\n\nvar (\n\tsimpleFields = []*spannerpb.StructType_Field{\n\t\t{\n\t\t\tName: \"Id\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"Value\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_STRING,\n\t\t\t},\n\t\t},\n\t}\n\tfullTypesFields = []*spannerpb.StructType_Field{\n\t\t{\n\t\t\tName: \"PKey\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_STRING,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"FTString\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_STRING,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"FTStringNull\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_STRING,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"FTBool\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_BOOL,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"FTBoolNull\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_BOOL,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"FTBytes\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_BYTES,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"FTBytesNull\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_BYTES,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"FTTimestamp\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_TIMESTAMP,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"FTTimestampNull\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_TIMESTAMP,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"FTInt\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"FTIntNull\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"FTFloat\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_FLOAT64,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"FTFloatNull\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_FLOAT64,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"FTDate\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_DATE,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"FTDateNull\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_DATE,\n\t\t\t},\n\t\t},\n\t}\n\tarrayTypesFields = []*spannerpb.StructType_Field{\n\t\t{\n\t\t\tName: \"Id\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"ArrayString\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_STRING,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"ArrayBool\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_BOOL,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"ArrayBytes\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_BYTES,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"ArrayTimestamp\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_TIMESTAMP,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"ArrayInt\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"ArrayFloat\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_FLOAT64,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"ArrayDate\",\n\t\t\tType: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_DATE,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n)\n\nfunc newTestServer() *server {\n\treturn NewFakeServer().(*server)\n}\n\nfunc assertGRPCError(t *testing.T, err error, code codes.Code, msg string) {\n\tt.Helper()\n\tst := status.Convert(err)\n\tif st.Code() != code {\n\t\tt.Errorf(\"expect code to be %v but got %v\", code, st.Code())\n\t}\n\tif st.Message() != msg {\n\t\tt.Errorf(\"unexpected error message: \\n %q\\n expected:\\n %q\", st.Message(), msg)\n\t}\n}\n\nfunc assertStatusCode(t *testing.T, err error, code codes.Code) {\n\tt.Helper()\n\tst := status.Convert(err)\n\tif st.Code() != code {\n\t\tt.Errorf(\"expect code to be %v but got %v: msg=%v\", code, st.Code(), st.Message())\n\t}\n}\n\nfunc assertResourceInfo(t *testing.T, err error, expected *errdetails.ResourceInfo) {\n\tt.Helper()\n\tst := status.Convert(err)\n\tdetails := st.Details()\n\tif len(details) != 1 {\n\t\tt.Fatalf(\"error should have a detail: %v\", len(details))\n\t}\n\tri, ok := details[0].(*errdetails.ResourceInfo)\n\tif !ok {\n\t\tt.Fatalf(\"error detail should be ResourceInfo: %T\", details[0])\n\t}\n\tif diff := cmp.Diff(expected, ri, protocmp.Transform()); diff != \"\" {\n\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t}\n}\n\nfunc testCreateSession(t *testing.T, s *server) (*spannerpb.Session, string) {\n\tname := fmt.Sprintf(\"projects/fake/instances/fakse/databases/%s\", uuidpkg.New().String())\n\tsession, err := s.CreateSession(context.Background(), &spannerpb.CreateSessionRequest{\n\t\tDatabase: name,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create session: %v\", err)\n\t}\n\treturn session, name\n}\n\nfunc testInvalidatedTransaction(t *testing.T, s *server, session *spannerpb.Session) *spannerpb.Transaction {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\tdefer cancel()\n\n\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\tSession: session.Name,\n\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t},\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif len(tx.Id) == 0 {\n\t\tt.Error(\"transaction id must not be empty\")\n\t}\n\n\tfor i := 0; i < 32; i++ {\n\t\ttx2, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif len(tx2.Id) == 0 {\n\t\t\tt.Error(\"transaction id must not be empty\")\n\t\t}\n\t}\n\n\treturn tx\n}\n\nfunc TestCreateSession(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\n\tvalidDatabaseName := \"projects/fake/instances/fake/databases/fake\"\n\n\tt.Run(\"Success\", func(t *testing.T) {\n\t\tfor i := 0; i < 3; i++ {\n\t\t\tsession, err := s.CreateSession(ctx, &spannerpb.CreateSessionRequest{\n\t\t\t\tDatabase: validDatabaseName,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif session.Name == \"\" {\n\t\t\t\tt.Error(\"session name must not be empty\")\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Invalid\", func(t *testing.T) {\n\t\tnames := []string{\n\t\t\t\"projects/fake/instances/fake/databases/\",\n\t\t\t\"projects/fake/instances//databases/fake\",\n\t\t\t\"projects//instances/fake/databases/fake\",\n\t\t\t\"xx/fake/instances/fake/databases/fake\",\n\t\t\t\"projects/fake/xx/fake/databases/fake\",\n\t\t\t\"projects/fake/instances/fake/xx/fake\",\n\t\t\t\"xxx\",\n\t\t}\n\t\tfor _, name := range names {\n\t\t\t_, err := s.CreateSession(ctx, &spannerpb.CreateSessionRequest{\n\t\t\t\tDatabase: name,\n\t\t\t})\n\t\t\tst := status.Convert(err)\n\t\t\tif want, got := codes.InvalidArgument, st.Code(); want != got {\n\t\t\t\tt.Errorf(\"expect %v but got %v for %v\", want, got, name)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestBatchCreateSessions(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\n\tvalidDatabaseName := \"projects/fake/instances/fake/databases/fake\"\n\n\tt.Run(\"Success\", func(t *testing.T) {\n\t\tfor i := 0; i < 3; i++ {\n\t\t\tsessions, err := s.BatchCreateSessions(ctx, &spannerpb.BatchCreateSessionsRequest{\n\t\t\t\tDatabase:     validDatabaseName,\n\t\t\t\tSessionCount: 3,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif len(sessions.Session) != 3 {\n\t\t\t\tt.Error(\"the number of sessions should be 3\")\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Invalid\", func(t *testing.T) {\n\t\tnames := []string{\n\t\t\t\"projects/fake/instances/fake/databases/\",\n\t\t\t\"projects/fake/instances//databases/fake\",\n\t\t\t\"projects//instances/fake/databases/fake\",\n\t\t\t\"xx/fake/instances/fake/databases/fake\",\n\t\t\t\"projects/fake/xx/fake/databases/fake\",\n\t\t\t\"projects/fake/instances/fake/xx/fake\",\n\t\t\t\"xxx\",\n\t\t}\n\t\tfor _, name := range names {\n\t\t\t_, err := s.BatchCreateSessions(ctx, &spannerpb.BatchCreateSessionsRequest{\n\t\t\t\tDatabase:     name,\n\t\t\t\tSessionCount: 1,\n\t\t\t})\n\t\t\tst := status.Convert(err)\n\t\t\tif want, got := codes.InvalidArgument, st.Code(); want != got {\n\t\t\t\tt.Errorf(\"expect %v but got %v for %v\", want, got, name)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestGetSession(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\tsession, _ := testCreateSession(t, s)\n\n\t_, err := s.GetSession(ctx, &spannerpb.GetSessionRequest{\n\t\tName: session.Name,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"GetSession must success: %v\", err)\n\n\t\t_, err = s.GetSession(ctx, &spannerpb.GetSessionRequest{\n\t\t\tName: \"xx\",\n\t\t})\n\t\tst := status.Convert(err)\n\t\tif want, got := codes.InvalidArgument, st.Code(); want != got {\n\t\t\tt.Errorf(\"expect %v but got %v\", want, got)\n\t\t}\n\t}\n\n\t_, err = s.GetSession(ctx, &spannerpb.GetSessionRequest{\n\t\tName: session.Name + \"x\",\n\t})\n\tst := status.Convert(err)\n\tif want, got := codes.NotFound, st.Code(); want != got {\n\t\tt.Errorf(\"expect %v but got %v\", want, got)\n\t}\n\texpectedRI := &errdetails.ResourceInfo{\n\t\tResourceType: \"type.googleapis.com/google.spanner.v1.Session\",\n\t\tResourceName: session.Name + \"x\",\n\t\tDescription:  \"Session does not exist.\",\n\t}\n\tassertResourceInfo(t, err, expectedRI)\n}\n\nfunc TestListSessions(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\n\tdbName1 := fmt.Sprintf(\"projects/fake/instances/fakse/databases/%s\", uuidpkg.New().String())\n\tdbName2 := fmt.Sprintf(\"projects/fake/instances/fakse/databases/%s\", uuidpkg.New().String())\n\n\tvar sessions1 []*spannerpb.Session\n\tvar sessions2 []*spannerpb.Session\n\n\tfor i := 0; i < 3; i++ {\n\t\tsession, err := s.CreateSession(context.Background(), &spannerpb.CreateSessionRequest{\n\t\t\tDatabase: dbName1,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to create session: %v\", err)\n\t\t}\n\t\tsessions1 = append(sessions1, session)\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tsession, err := s.CreateSession(context.Background(), &spannerpb.CreateSessionRequest{\n\t\t\tDatabase: dbName2,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to create session: %v\", err)\n\t\t}\n\t\tsessions2 = append(sessions2, session)\n\t}\n\n\tres1, err := s.ListSessions(ctx, &spannerpb.ListSessionsRequest{\n\t\tDatabase: dbName1,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"ListSession must succeed: %v\", err)\n\t}\n\tfor _, s := range res1.Sessions {\n\t\tvar found bool\n\t\tfor _, s2 := range sessions1 {\n\t\t\tif s.Name == s2.Name {\n\t\t\t\tfound = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\tt.Errorf(\"session %s not found\", s.Name)\n\t\t}\n\t}\n\n\tres2, err := s.ListSessions(ctx, &spannerpb.ListSessionsRequest{\n\t\tDatabase: dbName2,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"ListSession must succeed: %v\", err)\n\t}\n\tfor _, s := range res2.Sessions {\n\t\tvar found bool\n\t\tfor _, s2 := range sessions2 {\n\t\t\tif s.Name == s2.Name {\n\t\t\t\tfound = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\tt.Errorf(\"session %s not found\", s.Name)\n\t\t}\n\t}\n}\n\nfunc TestDeleteSession(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\tsession, _ := testCreateSession(t, s)\n\n\t_, err := s.DeleteSession(ctx, &spannerpb.DeleteSessionRequest{\n\t\tName: session.Name,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"DeleteSession must success: %v\", err)\n\t}\n\n\t_, err = s.DeleteSession(ctx, &spannerpb.DeleteSessionRequest{\n\t\tName: session.Name,\n\t})\n\tst := status.Convert(err)\n\tif want, got := codes.NotFound, st.Code(); want != got {\n\t\tt.Errorf(\"expect %v but got %v\", want, got)\n\t}\n\texpectedRI := &errdetails.ResourceInfo{\n\t\tResourceType: \"type.googleapis.com/google.spanner.v1.Session\",\n\t\tResourceName: session.Name,\n\t\tDescription:  \"Session does not exist.\",\n\t}\n\tassertResourceInfo(t, err, expectedRI)\n}\n\nfunc TestBeginTransaction(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\n\tt.Run(\"Success\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif len(tx.Id) == 0 {\n\t\t\tt.Error(\"transaction id must not be empty\")\n\t\t}\n\n\t\ttx2, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadOnly_{\n\t\t\t\t\tReadOnly: &spannerpb.TransactionOptions_ReadOnly{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif len(tx2.Id) == 0 {\n\t\t\tt.Error(\"transaction id must not be empty\")\n\t\t}\n\n\t\ttx3, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_PartitionedDml_{\n\t\t\t\t\tPartitionedDml: &spannerpb.TransactionOptions_PartitionedDml{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif len(tx3.Id) == 0 {\n\t\t\tt.Error(\"transaction id must not be empty\")\n\t\t}\n\t})\n\n\tt.Run(\"NoOption\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\t_, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t})\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"unexpected success\")\n\t\t}\n\t\tst := status.Convert(err)\n\t\tif st.Code() != codes.InvalidArgument {\n\t\t\tt.Fatalf(\"err codes must be InvalidArgument but got %v\", st.Code())\n\t\t}\n\t})\n}\n\nfunc NewFakeExecuteStreamingSqlServer(ctx context.Context) *fakeExecuteStreamingSqlServer {\n\treturn &fakeExecuteStreamingSqlServer{ctx: ctx}\n}\n\ntype fakeExecuteStreamingSqlServer struct {\n\tctx    context.Context\n\tcancel func()\n\n\tsets []*spannerpb.PartialResultSet\n\n\tgrpc.ServerStream\n}\n\nfunc (s *fakeExecuteStreamingSqlServer) Send(set *spannerpb.PartialResultSet) error {\n\ts.sets = append(s.sets, set)\n\treturn nil\n}\n\nfunc (s *fakeExecuteStreamingSqlServer) Context() context.Context {\n\tif s.ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn s.ctx\n}\n\nfunc TestExecuteStreamingSql_Success(t *testing.T) {\n\tctx := context.Background()\n\ts := newTestServer()\n\tsession, dbName := testCreateSession(t, s)\n\n\t// TODO: prepare initial data\n\tdb, ok := s.db[dbName]\n\tif !ok {\n\t\tt.Fatalf(\"database not found\")\n\t}\n\tfor _, s := range allSchema {\n\t\tddls := parseDDL(t, s)\n\t\tfor _, ddl := range ddls {\n\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t}\n\t}\n\tfor _, query := range []string{\n\t\t`INSERT INTO Simple VALUES(100, \"xxx\")`,\n\t\t`INSERT INTO Simple VALUES(200, \"yyy\")`,\n\t\t`INSERT INTO Simple VALUES(300, \"zzz\")`,\n\n\t\t`INSERT INTO CompositePrimaryKeys VALUES(1, \"aaa\", 1, 0, \"x1\", \"y1\", \"z\")`,\n\t\t`INSERT INTO CompositePrimaryKeys VALUES(2, \"bbb\", 2, 0, \"x1\", \"y2\", \"z\")`,\n\t\t`INSERT INTO CompositePrimaryKeys VALUES(3, \"bbb\", 3, 0, \"x1\", \"y3\", \"z\")`,\n\t\t`INSERT INTO CompositePrimaryKeys VALUES(4, \"ccc\", 3, 0, \"x2\", \"y4\", \"z\")`,\n\t\t`INSERT INTO CompositePrimaryKeys VALUES(5, \"ccc\", 4, 0, \"x2\", \"y5\", \"z\")`,\n\n\t\t`INSERT INTO FullTypes VALUES(\"xxx\",\n            \"xxx\", \"xxx\",\n            true, true,\n            \"eHl6\", \"eHl6\",\n            \"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.123456789Z\",\n            100, 100,\n            0.5, 0.5,\n            \"2012-03-04\", \"2012-03-04\"\n        )`,\n\n\t\t`INSERT INTO ArrayTypes VALUES(100,\n           json_array(\"xxx1\", \"xxx2\"),\n           json_array(true, false),\n           json_array(\"eHl6\", \"eHl6\"),\n           json_array(\"2012-03-04T12:34:56.123456789Z\", \"2012-03-04T12:34:56.999999999Z\"),\n           json_array(1, 2),\n           json_array(0.1, 0.2),\n           json_array(\"2012-03-04\", \"2012-03-05\")\n        )`,\n\t} {\n\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t}\n\t}\n\n\ttable := map[string]struct {\n\t\tsql      string\n\t\ttypes    map[string]*spannerpb.Type\n\t\tparams   *structpb.Struct\n\t\tfields   []*spannerpb.StructType_Field\n\t\texpected [][]*structpb.Value\n\t}{\n\t\t\"Simple\": {\n\t\t\tsql:    `SELECT * FROM Simple`,\n\t\t\tfields: simpleFields,\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"100\"), makeStringValue(\"xxx\"),\n\t\t\t\t\tmakeStringValue(\"200\"), makeStringValue(\"yyy\"),\n\t\t\t\t\tmakeStringValue(\"300\"), makeStringValue(\"zzz\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t\"Simple_QueryParam\": {\n\t\t\tsql: `SELECT * FROM Simple WHERE Id IN (@foo, @bar)`,\n\t\t\ttypes: map[string]*spannerpb.Type{\n\t\t\t\t\"foo\": &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t},\n\t\t\t\t\"bar\": &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t},\n\t\t\t},\n\t\t\tparams: &structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"foo\": makeStringValue(\"100\"),\n\t\t\t\t\t\"bar\": makeStringValue(\"200\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfields: simpleFields,\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"100\"), makeStringValue(\"xxx\"),\n\t\t\t\t\tmakeStringValue(\"200\"), makeStringValue(\"yyy\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t\"Simple_QueryParam_ImplicitTypeAsInt64\": {\n\t\t\tsql: `SELECT @foo`,\n\t\t\tparams: &structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"foo\": makeStringValue(\"100\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfields: []*spannerpb.StructType_Field{\n\t\t\t\t{\n\t\t\t\t\tName: \"\",\n\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t\"Simple_UnusedTypes\": {\n\t\t\tsql: `SELECT 100 v`,\n\t\t\ttypes: map[string]*spannerpb.Type{\n\t\t\t\t\"foo\": &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t},\n\t\t\t},\n\t\t\tfields: []*spannerpb.StructType_Field{\n\t\t\t\t{\n\t\t\t\t\tName: \"v\",\n\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t\"Simple_UnusedParams\": {\n\t\t\tsql: `SELECT 100 v`,\n\t\t\ttypes: map[string]*spannerpb.Type{\n\t\t\t\t\"foo\": &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t},\n\t\t\t},\n\t\t\tparams: &structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"foo\": makeStringValue(\"200\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfields: []*spannerpb.StructType_Field{\n\t\t\t\t{\n\t\t\t\t\tName: \"v\",\n\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t\"FromUnnest_ArrayLiteral\": {\n\t\t\tsql: `SELECT x, y FROM UNNEST ([\"xxx\", \"yyy\"]) AS x WITH OFFSET y`,\n\t\t\tfields: []*spannerpb.StructType_Field{\n\t\t\t\t{\n\t\t\t\t\tName: \"x\",\n\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_STRING,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"y\",\n\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"xxx\"), makeStringValue(\"0\"),\n\t\t\t\t\tmakeStringValue(\"yyy\"), makeStringValue(\"1\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"FromUnnest_Params\": {\n\t\t\tsql: `SELECT x, y FROM UNNEST (@foo) AS x WITH OFFSET y`,\n\t\t\ttypes: map[string]*spannerpb.Type{\n\t\t\t\t\"foo\": &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tparams: &structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"foo\": makeListValueAsValue(makeListValue(makeStringValue(\"100\"), makeStringValue(\"200\"))),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfields: []*spannerpb.StructType_Field{\n\t\t\t\t{\n\t\t\t\t\tName: \"x\",\n\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"y\",\n\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"100\"), makeStringValue(\"0\"),\n\t\t\t\t\tmakeStringValue(\"200\"), makeStringValue(\"1\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t\"Simple_Unnest_Array\": {\n\t\t\tsql:    `SELECT * FROM Simple WHERE Id IN UNNEST([100, 200])`,\n\t\t\tfields: simpleFields,\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"100\"), makeStringValue(\"xxx\"),\n\t\t\t\t\tmakeStringValue(\"200\"), makeStringValue(\"yyy\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t\"Simple_Unnest_Params\": {\n\t\t\tsql: `SELECT * FROM Simple WHERE Id IN UNNEST(@ids)`,\n\t\t\ttypes: map[string]*spannerpb.Type{\n\t\t\t\t\"ids\": &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tparams: &structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"ids\": makeListValueAsValue(makeListValue(makeStringValue(\"100\"), makeStringValue(\"200\"))),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfields: simpleFields,\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"100\"), makeStringValue(\"xxx\"),\n\t\t\t\t\tmakeStringValue(\"200\"), makeStringValue(\"yyy\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t\"Simple_Unnest_Params_EmptyArray\": {\n\t\t\tsql: `SELECT * FROM Simple WHERE Id IN UNNEST(@ids)`,\n\t\t\ttypes: map[string]*spannerpb.Type{\n\t\t\t\t\"ids\": &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tparams: &structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"ids\": makeNullValue(),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfields: nil,\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{},\n\t\t\t},\n\t\t},\n\n\t\t\"CompositePrimaryKeys_Condition\": {\n\t\t\tsql: `SELECT Id, PKey1, PKey2 FROM CompositePrimaryKeys WHERE PKey1 = \"bbb\" AND (PKey2 = 3 OR PKey2 = 4)`,\n\t\t\tfields: []*spannerpb.StructType_Field{\n\t\t\t\t{\n\t\t\t\t\tName: \"Id\",\n\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"PKey1\",\n\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_STRING,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"PKey2\",\n\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{makeStringValue(\"3\"), makeStringValue(\"bbb\"), makeStringValue(\"3\")},\n\t\t\t},\n\t\t},\n\t\t\"CompositePrimaryKeys_Condition2\": {\n\t\t\tsql: fmt.Sprintf(`SELECT %s FROM CompositePrimaryKeys`,\n\t\t\t\tstrings.Join(strings.Split(strings.Repeat(\"XYZ\", 10), \"\"), \", \")),\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y2\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y2\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y2\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y2\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y2\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y2\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y2\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y2\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y2\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y2\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y3\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y3\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y3\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y3\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y3\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y3\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y3\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y3\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y3\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x1\"), makeStringValue(\"y3\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y4\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y4\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y4\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y4\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y4\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y4\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y4\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y4\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y4\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y4\"), makeStringValue(\"z\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t\tmakeStringValue(\"x2\"), makeStringValue(\"y5\"), makeStringValue(\"z\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ArrayOfStruct\": {\n\t\t\tsql: `SELECT ARRAY(SELECT STRUCT<Id int64, Value string>(1,\"xx\") x)`,\n\t\t\tfields: []*spannerpb.StructType_Field{\n\t\t\t\t{\n\t\t\t\t\tName: \"\",\n\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\t\t\tCode: spannerpb.TypeCode_STRUCT,\n\t\t\t\t\t\t\tStructType: &spannerpb.StructType{\n\t\t\t\t\t\t\t\tFields: []*spannerpb.StructType_Field{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tName: \"Id\",\n\t\t\t\t\t\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tName: \"Value\",\n\t\t\t\t\t\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\t\t\t\t\t\tCode: spannerpb.TypeCode_STRING,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\t\tmakeStructValue(map[string]*structpb.Value{\n\t\t\t\t\t\t\t\"Id\":    makeStringValue(\"1\"),\n\t\t\t\t\t\t\t\"Value\": makeStringValue(\"xx\"),\n\t\t\t\t\t\t}),\n\t\t\t\t\t)),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ArrayOfStruct_FullTypes\": {\n\t\t\tsql: `SELECT ARRAY(SELECT AS STRUCT * FROM FullTypes)`,\n\t\t\tfields: []*spannerpb.StructType_Field{\n\t\t\t\t{\n\t\t\t\t\tName: \"\",\n\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\t\t\tCode: spannerpb.TypeCode_STRUCT,\n\t\t\t\t\t\t\tStructType: &spannerpb.StructType{\n\t\t\t\t\t\t\t\tFields: fullTypesFields,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// fields: simpleFields,\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\t\tmakeStructValue(map[string]*structpb.Value{\n\t\t\t\t\t\t\t\"PKey\":            makeStringValue(\"xxx\"),\n\t\t\t\t\t\t\t\"FTString\":        makeStringValue(\"xxx\"),\n\t\t\t\t\t\t\t\"FTStringNull\":    makeStringValue(\"xxx\"),\n\t\t\t\t\t\t\t\"FTBool\":          makeBoolValue(true),\n\t\t\t\t\t\t\t\"FTBoolNull\":      makeBoolValue(true),\n\t\t\t\t\t\t\t\"FTBytes\":         makeStringValue(\"eHl6\"),\n\t\t\t\t\t\t\t\"FTBytesNull\":     makeStringValue(\"eHl6\"),\n\t\t\t\t\t\t\t\"FTTimestamp\":     makeStringValue(\"2012-03-04T12:34:56.123456789Z\"),\n\t\t\t\t\t\t\t\"FTTimestampNull\": makeStringValue(\"2012-03-04T12:34:56.123456789Z\"),\n\t\t\t\t\t\t\t\"FTInt\":           makeStringValue(\"100\"),\n\t\t\t\t\t\t\t\"FTIntNull\":       makeStringValue(\"100\"),\n\t\t\t\t\t\t\t\"FTFloat\":         makeNumberValue(0.5),\n\t\t\t\t\t\t\t\"FTFloatNull\":     makeNumberValue(0.5),\n\t\t\t\t\t\t\t\"FTDate\":          makeStringValue(\"2012-03-04\"),\n\t\t\t\t\t\t\t\"FTDateNull\":      makeStringValue(\"2012-03-04\"),\n\t\t\t\t\t\t}),\n\t\t\t\t\t)),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ArrayOfStruct_ArrayTypes\": {\n\t\t\tsql: `SELECT ARRAY(SELECT AS STRUCT * FROM ArrayTypes)`,\n\t\t\tfields: []*spannerpb.StructType_Field{\n\t\t\t\t{\n\t\t\t\t\tName: \"\",\n\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\t\t\tCode: spannerpb.TypeCode_STRUCT,\n\t\t\t\t\t\t\tStructType: &spannerpb.StructType{\n\t\t\t\t\t\t\t\tFields: arrayTypesFields,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// fields: simpleFields,\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\t\tmakeStructValue(map[string]*structpb.Value{\n\t\t\t\t\t\t\t\"Id\": makeStringValue(\"100\"),\n\t\t\t\t\t\t\t\"ArrayString\": makeListValueAsValue(makeListValue(\n\t\t\t\t\t\t\t\tmakeStringValue(\"xxx1\"), makeStringValue(\"xxx2\"),\n\t\t\t\t\t\t\t)),\n\t\t\t\t\t\t\t\"ArrayBool\": makeListValueAsValue(makeListValue(\n\t\t\t\t\t\t\t\tmakeBoolValue(true), makeBoolValue(false),\n\t\t\t\t\t\t\t)),\n\t\t\t\t\t\t\t\"ArrayBytes\": makeListValueAsValue(makeListValue(\n\t\t\t\t\t\t\t\tmakeStringValue(\"eHl6\"), makeStringValue(\"eHl6\"),\n\t\t\t\t\t\t\t)),\n\t\t\t\t\t\t\t\"ArrayTimestamp\": makeListValueAsValue(makeListValue(\n\t\t\t\t\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"),\n\t\t\t\t\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.999999999Z\"),\n\t\t\t\t\t\t\t)),\n\t\t\t\t\t\t\t\"ArrayInt\": makeListValueAsValue(makeListValue(\n\t\t\t\t\t\t\t\tmakeStringValue(\"1\"), makeStringValue(\"2\"),\n\t\t\t\t\t\t\t)),\n\t\t\t\t\t\t\t\"ArrayFloat\": makeListValueAsValue(makeListValue(\n\t\t\t\t\t\t\t\tmakeNumberValue(0.1), makeNumberValue(0.2),\n\t\t\t\t\t\t\t)),\n\t\t\t\t\t\t\t\"ArrayDate\": makeListValueAsValue(makeListValue(\n\t\t\t\t\t\t\t\tmakeStringValue(\"2012-03-04\"), makeStringValue(\"2012-03-05\"),\n\t\t\t\t\t\t\t)),\n\t\t\t\t\t\t}),\n\t\t\t\t\t)),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tc := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tfake := &fakeExecuteStreamingSqlServer{}\n\t\t\tif err := s.ExecuteStreamingSql(&spannerpb.ExecuteSqlRequest{\n\t\t\t\tSession:     session.Name,\n\t\t\t\tTransaction: &spannerpb.TransactionSelector{},\n\t\t\t\tSql:         tc.sql,\n\t\t\t\tParamTypes:  tc.types,\n\t\t\t\tParams:      tc.params,\n\t\t\t}, fake); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\tvar results [][]*structpb.Value\n\t\t\tfor _, set := range fake.sets {\n\t\t\t\tresults = append(results, set.Values)\n\t\t\t}\n\n\t\t\tif tc.fields != nil {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Fatalf(\"unexpected number of results\")\n\t\t\t\t}\n\t\t\t\tif diff := cmp.Diff(tc.fields, fake.sets[0].Metadata.RowType.Fields, protocmp.Transform()); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(tc.expected, results, protocmp.Transform()); diff != \"\" {\n\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestStreamingRead_ValueType(t *testing.T) {\n\ttable := map[string]struct {\n\t\ttable    string\n\t\twcols    []string\n\t\tvalues   []*structpb.Value\n\t\trcols    []string\n\t\tfields   []*spannerpb.StructType_Field\n\t\texpected []*structpb.Value\n\t}{\n\t\t\"Simple\": {\n\t\t\ttable: \"Simple\",\n\t\t\twcols: []string{\"Id\", \"Value\"},\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"300\"),\n\t\t\t\tmakeStringValue(\"zzz\"),\n\t\t\t},\n\t\t\trcols: []string{\"Id\", \"Value\"},\n\t\t\tfields: []*spannerpb.StructType_Field{\n\t\t\t\t{\n\t\t\t\t\tName: \"Id\",\n\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"Value\",\n\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_STRING,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"300\"),\n\t\t\t\tmakeStringValue(\"zzz\"),\n\t\t\t},\n\t\t},\n\t\t\"FullTypes\": {\n\t\t\ttable: \"FullTypes\",\n\t\t\twcols: fullTypesKeys,\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"xxx\"),  // PKey STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"xxx\"),  // FTString STRING(32) NOT NULL,\n\t\t\t\tmakeNullValue(),         // FTStringNull STRING(32),\n\t\t\t\tmakeBoolValue(true),     // FTBool BOOL NOT NULL,\n\t\t\t\tmakeNullValue(),         // FTBoolNull BOOL,\n\t\t\t\tmakeStringValue(\"eHh4\"), // FTBytes BYTES(32) NOT NULL,\n\t\t\t\tmakeNullValue(),         // FTBytesNull BYTES(32),\n\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestamp TIMESTAMP NOT NULL,\n\t\t\t\tmakeNullValue(),               // FTTimestampNull TIMESTAMP,\n\t\t\t\tmakeStringValue(\"100\"),        // FTInt INT64 NOT NULL,\n\t\t\t\tmakeNullValue(),               // FTIntNull INT64,\n\t\t\t\tmakeNumberValue(0.5),          // FTFloat FLOAT64 NOT NULL,\n\t\t\t\tmakeNullValue(),               // FTFloatNull FLOAT64,\n\t\t\t\tmakeStringValue(\"2012-03-04\"), // FTDate DATE NOT NULL,\n\t\t\t\tmakeNullValue(),               // FTDateNull DATE,\n\t\t\t},\n\t\t\trcols:  fullTypesKeys,\n\t\t\tfields: fullTypesFields,\n\t\t\texpected: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"xxx\"),  // PKey STRING(32) NOT NULL,\n\t\t\t\tmakeStringValue(\"xxx\"),  // FTString STRING(32) NOT NULL,\n\t\t\t\tmakeNullValue(),         // FTStringNull STRING(32),\n\t\t\t\tmakeBoolValue(true),     // FTBool BOOL NOT NULL,\n\t\t\t\tmakeNullValue(),         // FTBoolNull BOOL,\n\t\t\t\tmakeStringValue(\"eHh4\"), // FTBytes BYTES(32) NOT NULL,\n\t\t\t\tmakeNullValue(),         // FTBytesNull BYTES(32),\n\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestamp TIMESTAMP NOT NULL,\n\t\t\t\tmakeNullValue(),               // FTTimestampNull TIMESTAMP,\n\t\t\t\tmakeStringValue(\"100\"),        // FTInt INT64 NOT NULL,\n\t\t\t\tmakeNullValue(),               // FTIntNull INT64,\n\t\t\t\tmakeNumberValue(0.5),          // FTFloat FLOAT64 NOT NULL,\n\t\t\t\tmakeNullValue(),               // FTFloatNull FLOAT64,\n\t\t\t\tmakeStringValue(\"2012-03-04\"), // FTDate DATE NOT NULL,\n\t\t\t\tmakeNullValue(),               // FTDateNull DATE,\n\t\t\t},\n\t\t},\n\n\t\t// Spanner returns array but it possiblly includes NullValue in the elements\n\t\t\"ArrayTypes_NonNull\": {\n\t\t\ttable: \"ArrayTypes\",\n\t\t\twcols: arrayTypesKeys,\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t\tmakeNullValue(),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeBoolValue(true),\n\t\t\t\t\tmakeNullValue(),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"eHh4\"),\n\t\t\t\t\tmakeNullValue(),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"),\n\t\t\t\t\tmakeNullValue(),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeNullValue(),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeNumberValue(0.5),\n\t\t\t\t\tmakeNullValue(),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"2012-03-04\"),\n\t\t\t\t\tmakeNullValue(),\n\t\t\t\t)),\n\t\t\t},\n\t\t\trcols:  arrayTypesKeys,\n\t\t\tfields: arrayTypesFields,\n\t\t\texpected: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\t\tmakeNullValue(),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeBoolValue(true),\n\t\t\t\t\tmakeNullValue(),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"eHh4\"),\n\t\t\t\t\tmakeNullValue(),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"),\n\t\t\t\t\tmakeNullValue(),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\tmakeNullValue(),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeNumberValue(0.5),\n\t\t\t\t\tmakeNullValue(),\n\t\t\t\t)),\n\t\t\t\tmakeListValueAsValue(makeListValue(\n\t\t\t\t\tmakeStringValue(\"2012-03-04\"),\n\t\t\t\t\tmakeNullValue(),\n\t\t\t\t)),\n\t\t\t},\n\t\t},\n\n\t\t// Spanner returns NullValue if the value is null\n\t\t\"ArrayTypes_Null\": {\n\t\t\ttable: \"ArrayTypes\",\n\t\t\twcols: arrayTypesKeys,\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"101\"),\n\t\t\t\tmakeNullValue(),\n\t\t\t\tmakeNullValue(),\n\t\t\t\tmakeNullValue(),\n\t\t\t\tmakeNullValue(),\n\t\t\t\tmakeNullValue(),\n\t\t\t\tmakeNullValue(),\n\t\t\t\tmakeNullValue(),\n\t\t\t},\n\t\t\trcols:  arrayTypesKeys,\n\t\t\tfields: arrayTypesFields,\n\t\t\texpected: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"101\"),\n\t\t\t\tmakeNullValue(),\n\t\t\t\tmakeNullValue(),\n\t\t\t\tmakeNullValue(),\n\t\t\t\tmakeNullValue(),\n\t\t\t\tmakeNullValue(),\n\t\t\t\tmakeNullValue(),\n\t\t\t\tmakeNullValue(),\n\t\t\t},\n\t\t},\n\n\t\t// Spanner returns empty list as array if the value is empty not null\n\t\t\"ArrayTypes_Empty\": {\n\t\t\ttable: \"ArrayTypes\",\n\t\t\twcols: arrayTypesKeys,\n\t\t\tvalues: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeListValueAsValue(&structpb.ListValue{Values: []*structpb.Value{}}),\n\t\t\t\tmakeListValueAsValue(&structpb.ListValue{Values: []*structpb.Value{}}),\n\t\t\t\tmakeListValueAsValue(&structpb.ListValue{Values: []*structpb.Value{}}),\n\t\t\t\tmakeListValueAsValue(&structpb.ListValue{Values: []*structpb.Value{}}),\n\t\t\t\tmakeListValueAsValue(&structpb.ListValue{Values: []*structpb.Value{}}),\n\t\t\t\tmakeListValueAsValue(&structpb.ListValue{Values: []*structpb.Value{}}),\n\t\t\t\tmakeListValueAsValue(&structpb.ListValue{Values: []*structpb.Value{}}),\n\t\t\t},\n\t\t\trcols:  arrayTypesKeys,\n\t\t\tfields: arrayTypesFields,\n\t\t\texpected: []*structpb.Value{\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeListValueAsValue(&structpb.ListValue{Values: []*structpb.Value{}}),\n\t\t\t\tmakeListValueAsValue(&structpb.ListValue{Values: []*structpb.Value{}}),\n\t\t\t\tmakeListValueAsValue(&structpb.ListValue{Values: []*structpb.Value{}}),\n\t\t\t\tmakeListValueAsValue(&structpb.ListValue{Values: []*structpb.Value{}}),\n\t\t\t\tmakeListValueAsValue(&structpb.ListValue{Values: []*structpb.Value{}}),\n\t\t\t\tmakeListValueAsValue(&structpb.ListValue{Values: []*structpb.Value{}}),\n\t\t\t\tmakeListValueAsValue(&structpb.ListValue{Values: []*structpb.Value{}}),\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tc := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx := context.Background()\n\t\t\ts := newTestServer()\n\t\t\tsession, dbName := testCreateSession(t, s)\n\n\t\t\tdb, ok := s.db[dbName]\n\t\t\tif !ok {\n\t\t\t\tt.Fatalf(\"database not found\")\n\t\t\t}\n\t\t\tfor _, s := range allSchema {\n\t\t\t\tddls := parseDDL(t, s)\n\t\t\t\tfor _, ddl := range ddls {\n\t\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t_, err := s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\t\tSession: session.Name,\n\t\t\t\tTransaction: &spannerpb.CommitRequest_SingleUseTransaction{\n\t\t\t\t\tSingleUseTransaction: &spannerpb.TransactionOptions{\n\t\t\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tMutations: []*spannerpb.Mutation{\n\t\t\t\t\t{\n\t\t\t\t\t\tOperation: &spannerpb.Mutation_Insert{\n\t\t\t\t\t\t\tInsert: &spannerpb.Mutation_Write{\n\t\t\t\t\t\t\t\tTable:   tc.table,\n\t\t\t\t\t\t\t\tColumns: tc.wcols,\n\t\t\t\t\t\t\t\tValues: []*structpb.ListValue{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tValues: tc.values,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}},\n\t\t\t\t},\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"commit failed: %v\", err)\n\t\t\t}\n\n\t\t\tfake := &fakeExecuteStreamingSqlServer{}\n\t\t\tif err := s.StreamingRead(&spannerpb.ReadRequest{\n\t\t\t\tSession:     session.Name,\n\t\t\t\tTransaction: &spannerpb.TransactionSelector{},\n\t\t\t\tTable:       tc.table,\n\t\t\t\tColumns:     tc.rcols,\n\t\t\t\tKeySet:      &spannerpb.KeySet{All: true},\n\t\t\t}, fake); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\tvar results [][]*structpb.Value\n\t\t\tfor _, set := range fake.sets {\n\t\t\t\tresults = append(results, set.Values)\n\t\t\t}\n\n\t\t\tif len(results) != 1 {\n\t\t\t\tt.Errorf(\"results should be 1 record but got %v\", len(results))\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(tc.fields, fake.sets[0].Metadata.RowType.Fields, protocmp.Transform()); diff != \"\" {\n\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(tc.expected, results[0], protocmp.Transform()); diff != \"\" {\n\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestExecuteStreamingSql_Error(t *testing.T) {\n\tctx := context.Background()\n\ts := newTestServer()\n\tsession, dbName := testCreateSession(t, s)\n\n\t// TODO: prepare initial data\n\tdb, ok := s.db[dbName]\n\tif !ok {\n\t\tt.Fatalf(\"database not found\")\n\t}\n\tfor _, s := range allSchema {\n\t\tddls := parseDDL(t, s)\n\t\tfor _, ddl := range ddls {\n\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t}\n\t}\n\n\ttable := map[string]struct {\n\t\tsql    string\n\t\ttypes  map[string]*spannerpb.Type\n\t\tparams *structpb.Struct\n\t\tcode   codes.Code\n\t\tmsg    *regexp.Regexp\n\t}{\n\t\t\"NoParameter\": {\n\t\t\tsql:    `SELECT @foo`,\n\t\t\ttypes:  nil,\n\t\t\tparams: nil,\n\t\t\tcode:   codes.InvalidArgument,\n\t\t\tmsg:    regexp.MustCompile(`No parameter found for binding: foo`),\n\t\t},\n\t\t\"NoType\": {\n\t\t\tsql:   `SELECT @foo`,\n\t\t\ttypes: nil,\n\t\t\tparams: &structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"foo\": &structpb.Value{\n\t\t\t\t\t\tKind: &structpb.Value_StringValue{StringValue: \"x\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tcode: codes.InvalidArgument,\n\t\t\tmsg:  regexp.MustCompile(`Invalid value for bind parameter foo: Expected INT64.`),\n\t\t},\n\n\t\t\"InvalidValueForBinding\": {\n\t\t\tsql: `SELECT @foo`,\n\t\t\tparams: &structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"foo\": &structpb.Value{\n\t\t\t\t\t\tKind: &structpb.Value_StringValue{StringValue: \"x\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tcode: codes.InvalidArgument,\n\t\t\tmsg:  regexp.MustCompile(`Invalid value for bind parameter foo: Expected INT64.`),\n\t\t},\n\n\t\t\"EmptySQL\": {\n\t\t\tsql:  ``,\n\t\t\tcode: codes.InvalidArgument,\n\t\t\tmsg:  regexp.MustCompile(`^Invalid ExecuteStreamingSql request.`),\n\t\t},\n\t\t\"SQLSyntxError\": {\n\t\t\tsql:  `SELECT`,\n\t\t\tcode: codes.InvalidArgument,\n\t\t\tmsg:  regexp.MustCompile(`^Syntax error: .+`),\n\t\t},\n\n\t\t\"BindStruct\": {\n\t\t\tsql: `SELECT @bar`,\n\t\t\ttypes: map[string]*spannerpb.Type{\n\t\t\t\t\"bar\": &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_STRUCT,\n\t\t\t\t\tStructType: &spannerpb.StructType{\n\t\t\t\t\t\tFields: []*spannerpb.StructType_Field{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"xxx\",\n\t\t\t\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\t\t\t\tCode: spannerpb.TypeCode_STRING,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"yyy\",\n\t\t\t\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tparams: &structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"bar\": &structpb.Value{\n\t\t\t\t\t\tKind: &structpb.Value_StructValue{StructValue: &structpb.Struct{\n\t\t\t\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\t\t\t\"xxx\": &structpb.Value{\n\t\t\t\t\t\t\t\t\tKind: &structpb.Value_StringValue{StringValue: \"xxx\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"yyy\": &structpb.Value{\n\t\t\t\t\t\t\t\t\tKind: &structpb.Value_StringValue{StringValue: \"100\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tcode: codes.InvalidArgument,\n\t\t\tmsg:  regexp.MustCompile(`Invalid value for bind parameter bar: Expected STRUCT<xxx STRING, yyy INT64>`),\n\t\t},\n\n\t\t\"BindArrayStruct\": {\n\t\t\tsql: `SELECT @bar`,\n\t\t\tparams: &structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"bar\": &structpb.Value{\n\t\t\t\t\t\tKind: &structpb.Value_ListValue{\n\t\t\t\t\t\t\tListValue: &structpb.ListValue{\n\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tKind: &structpb.Value_StructValue{StructValue: &structpb.Struct{\n\t\t\t\t\t\t\t\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\t\t\t\t\t\t\t\"xxx\": &structpb.Value{\n\t\t\t\t\t\t\t\t\t\t\t\t\tKind: &structpb.Value_StringValue{StringValue: \"xxx\"},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\"yyy\": &structpb.Value{\n\t\t\t\t\t\t\t\t\t\t\t\t\tKind: &structpb.Value_StringValue{StringValue: \"100\"},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t}},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\ttypes: map[string]*spannerpb.Type{\n\t\t\t\t\"bar\": &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_STRUCT,\n\t\t\t\t\t\tStructType: &spannerpb.StructType{\n\t\t\t\t\t\t\tFields: []*spannerpb.StructType_Field{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName: \"xxx\",\n\t\t\t\t\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\t\t\t\t\tCode: spannerpb.TypeCode_STRING,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName: \"yyy\",\n\t\t\t\t\t\t\t\t\tType: &spannerpb.Type{\n\t\t\t\t\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tcode: codes.InvalidArgument,\n\t\t\t// TODO: return correct error message\n\t\t\t// msg:  regexp.MustCompile(`Invalid value for array element bar[0]: Expected STRUCT<xxx STRING, yyy INT64>.`),\n\t\t\tmsg: regexp.MustCompile(`^Invalid value for bind parameter bar\\[0\\]: Expected STRUCT<xxx STRING, yyy INT64>.`),\n\t\t},\n\t}\n\n\tfor name, tc := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tfake := &fakeExecuteStreamingSqlServer{}\n\t\t\terr := s.ExecuteStreamingSql(&spannerpb.ExecuteSqlRequest{\n\t\t\t\tSession:     session.Name,\n\t\t\t\tTransaction: &spannerpb.TransactionSelector{},\n\t\t\t\tSql:         tc.sql,\n\t\t\t\tParamTypes:  tc.types,\n\t\t\t\tParams:      tc.params,\n\t\t\t}, fake)\n\t\t\tst := status.Convert(err)\n\t\t\tif st.Code() != tc.code {\n\t\t\t\tt.Errorf(\"expect code to be %v but got %v\", tc.code, st.Code())\n\t\t\t}\n\t\t\tif !tc.msg.MatchString(st.Message()) {\n\t\t\t\tt.Errorf(\"unexpected error message: \\n %q\\n expected:\\n %q\", st.Message(), tc.msg)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestExecuteSql_Success(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\n\tpreareDBAndSession := func(t *testing.T) *spannerpb.Session {\n\t\tsession, dbName := testCreateSession(t, s)\n\t\tdb, ok := s.db[dbName]\n\t\tif !ok {\n\t\t\tt.Fatalf(\"database not found\")\n\t\t}\n\t\tfor _, s := range allSchema {\n\t\t\tddls := parseDDL(t, s)\n\t\t\tfor _, ddl := range ddls {\n\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t}\n\t\t}\n\n\t\tfor _, query := range []string{\n\t\t\t`INSERT INTO Simple VALUES(100, \"xxx\")`,\n\t\t\t`INSERT INTO Simple VALUES(200, \"yyy\")`,\n\t\t\t`INSERT INTO Simple VALUES(300, \"zzz\")`,\n\t\t} {\n\t\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t\t}\n\t\t}\n\n\t\treturn session\n\t}\n\n\tbegin := func(session *spannerpb.Session) *spannerpb.Transaction {\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\treturn tx\n\t}\n\tcommit := func(session *spannerpb.Session, tx *spannerpb.Transaction) error {\n\t\t_, err := s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t})\n\t\treturn err\n\t}\n\n\ttable := map[string]struct {\n\t\tsql    string\n\t\ttypes  map[string]*spannerpb.Type\n\t\tparams *structpb.Struct\n\t\tfields []*spannerpb.StructType_Field\n\n\t\ttable    string\n\t\tcolumns  []string\n\t\texpected [][]*structpb.Value\n\t}{\n\t\t\"Update\": {\n\t\t\tsql:     `UPDATE Simple SET Value = \"xyz\" WHERE Id = 200`,\n\t\t\ttable:   \"Simple\",\n\t\t\tcolumns: []string{\"Id\", \"Value\"},\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"100\"), makeStringValue(\"xxx\"),\n\t\t\t\t\tmakeStringValue(\"200\"), makeStringValue(\"xyz\"),\n\t\t\t\t\tmakeStringValue(\"300\"), makeStringValue(\"zzz\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Update_ParamInWhere\": {\n\t\t\tsql: `UPDATE Simple SET Value = \"xyz\" WHERE Id = @id`,\n\t\t\ttypes: map[string]*spannerpb.Type{\n\t\t\t\t\"id\": &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t},\n\t\t\t},\n\t\t\tparams: &structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"id\": &structpb.Value{\n\t\t\t\t\t\tKind: &structpb.Value_StringValue{StringValue: \"200\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\ttable:   \"Simple\",\n\t\t\tcolumns: []string{\"Id\", \"Value\"},\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"100\"), makeStringValue(\"xxx\"),\n\t\t\t\t\tmakeStringValue(\"200\"), makeStringValue(\"xyz\"),\n\t\t\t\t\tmakeStringValue(\"300\"), makeStringValue(\"zzz\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tc := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithTimeout(ctx, 3*time.Second)\n\t\t\tdefer cancel()\n\n\t\t\tsession := preareDBAndSession(t)\n\t\t\ttx := begin(session)\n\n\t\t\tif _, err := s.ExecuteSql(ctx, &spannerpb.ExecuteSqlRequest{\n\t\t\t\tSession: session.Name,\n\t\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\t\tId: tx.Id,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSql:        tc.sql,\n\t\t\t\tParamTypes: tc.types,\n\t\t\t\tParams:     tc.params,\n\t\t\t}); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\tif err := commit(session, tx); err != nil {\n\t\t\t\tt.Fatalf(\"commit error: %v\", err)\n\t\t\t}\n\n\t\t\tfake := NewFakeExecuteStreamingSqlServer(ctx)\n\t\t\tif err := s.StreamingRead(&spannerpb.ReadRequest{\n\t\t\t\tSession: session.Name,\n\t\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\t\tSelector: &spannerpb.TransactionSelector_SingleUse{\n\t\t\t\t\t\tSingleUse: &spannerpb.TransactionOptions{\n\t\t\t\t\t\t\tMode: &spannerpb.TransactionOptions_ReadOnly_{\n\t\t\t\t\t\t\t\tReadOnly: &spannerpb.TransactionOptions_ReadOnly{},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tTable:   tc.table,\n\t\t\t\tColumns: tc.columns,\n\t\t\t\tKeySet:  &spannerpb.KeySet{All: true},\n\t\t\t}, fake); err != nil {\n\t\t\t\tt.Fatalf(\"Read failed: %v\", err)\n\t\t\t}\n\n\t\t\tvar results [][]*structpb.Value\n\t\t\tfor _, set := range fake.sets {\n\t\t\t\tresults = append(results, set.Values)\n\t\t\t}\n\t\t\tif diff := cmp.Diff(tc.expected, results, protocmp.Transform()); diff != \"\" {\n\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestExecuteSql_Error(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\tbegin := func(ctx context.Context, t *testing.T, session *spannerpb.Session) *spannerpb.Transaction {\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\treturn tx\n\t}\n\n\tt.Run(\"RollbackAfterRollback\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx := begin(ctx, t, session)\n\n\t\t_, err := s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.ExecuteSql(ctx, &spannerpb.ExecuteSqlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tSql: `UPDATE Simple SET Value = \"x\" WHERE Id = 1000`,\n\t\t})\n\t\texpected := \"Cannot start a read or query within a transaction after Commit() or Rollback() has been called.\"\n\t\tassertGRPCError(t, err, codes.FailedPrecondition, expected)\n\t})\n\n\tt.Run(\"CommitAfterRollback\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx := begin(ctx, t, session)\n\n\t\t_, err := s.Rollback(ctx, &spannerpb.RollbackRequest{\n\t\t\tSession:       session.Name,\n\t\t\tTransactionId: tx.Id,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.ExecuteSql(ctx, &spannerpb.ExecuteSqlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tSql: `UPDATE Simple SET Value = \"x\" WHERE Id = 1000`,\n\t\t})\n\t\texpected := \"Cannot start a read or query within a transaction after Commit() or Rollback() has been called.\"\n\t\tassertGRPCError(t, err, codes.FailedPrecondition, expected)\n\t})\n\n\tt.Run(\"ReadOnlyTransaction\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadOnly_{\n\t\t\t\t\tReadOnly: &spannerpb.TransactionOptions_ReadOnly{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.ExecuteSql(ctx, &spannerpb.ExecuteSqlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tSql: `UPDATE Simple SET Value = \"x\" WHERE Id = 1000`,\n\t\t})\n\t\texpected := \"DML statements can only be performed in a read-write transaction.\"\n\t\tassertGRPCError(t, err, codes.FailedPrecondition, expected)\n\t})\n\n\tt.Run(\"SingleUseTransaction\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\t_, err := s.ExecuteSql(ctx, &spannerpb.ExecuteSqlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_SingleUse{\n\t\t\t\t\tSingleUse: &spannerpb.TransactionOptions{\n\t\t\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tSql: `UPDATE Simple SET Value = \"x\" WHERE Id = 1000`,\n\t\t})\n\t\texpected := \"DML statements may not be performed in single-use transactions, to avoid replay.\"\n\t\tassertGRPCError(t, err, codes.InvalidArgument, expected)\n\t})\n}\n\nfunc TestExecuteSql_Transaction(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\n\tpreareDBAndSession := func(t *testing.T) *spannerpb.Session {\n\t\tsession, dbName := testCreateSession(t, s)\n\t\tdb, ok := s.db[dbName]\n\t\tif !ok {\n\t\t\tt.Fatalf(\"database not found\")\n\t\t}\n\t\tfor _, s := range allSchema {\n\t\t\tddls := parseDDL(t, s)\n\t\t\tfor _, ddl := range ddls {\n\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t}\n\t\t}\n\n\t\tfor _, query := range []string{\n\t\t\t`INSERT INTO Simple VALUES(100, \"xxx\")`,\n\t\t} {\n\t\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t\t}\n\t\t}\n\n\t\treturn session\n\t}\n\n\tbegin := func(session *spannerpb.Session) *spannerpb.Transaction {\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\treturn tx\n\t}\n\tcommit := func(session *spannerpb.Session, tx *spannerpb.Transaction) error {\n\t\t_, err := s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t})\n\t\treturn err\n\t}\n\treadSimple := func(ctx context.Context, session *spannerpb.Session, tx *spannerpb.Transaction) ([]*structpb.Value, error) {\n\t\tfake := NewFakeExecuteStreamingSqlServer(ctx)\n\t\terr := s.StreamingRead(&spannerpb.ReadRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tTable:   \"Simple\",\n\t\t\tColumns: []string{\"Id\", \"Value\"},\n\t\t\tKeySet:  &spannerpb.KeySet{All: true},\n\t\t}, fake)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar results []*structpb.Value\n\t\tfor _, set := range fake.sets {\n\t\t\tresults = append(results, set.Values...)\n\t\t}\n\n\t\treturn results, nil\n\t}\n\n\tinitialValueSet := []*structpb.Value{\n\t\tmakeStringValue(\"100\"),\n\t\tmakeStringValue(\"xxx\"),\n\t}\n\n\tt.Run(\"ReadBeforeCommit_SameTransaction\", func(t *testing.T) {\n\t\tsession := preareDBAndSession(t)\n\t\ttx := begin(session)\n\t\t_, err := s.ExecuteSql(ctx, &spannerpb.ExecuteSqlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tSql:        `UPDATE Simple SET Value = \"zzz\" WHERE Id = 100`,\n\t\t\tParams:     nil,\n\t\t\tParamTypes: nil,\n\t\t})\n\t\tassertStatusCode(t, err, codes.OK)\n\n\t\texpected := []*structpb.Value{\n\t\t\tmakeStringValue(\"100\"),\n\t\t\tmakeStringValue(\"zzz\"),\n\t\t}\n\n\t\tresults, err := readSimple(ctx, session, tx)\n\t\tassertStatusCode(t, err, codes.OK)\n\t\tif diff := cmp.Diff(results, expected, protocmp.Transform()); diff != \"\" {\n\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t}\n\n\t\tif err := commit(session, tx); err != nil {\n\t\t\tt.Fatalf(\"commit failed: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"ReadBeforeCommit_AnotherTransaction\", func(t *testing.T) {\n\t\tsession := preareDBAndSession(t)\n\t\ttx1 := begin(session)\n\t\ttx2 := begin(session)\n\t\tdefer commit(session, tx1)\n\t\tdefer commit(session, tx2)\n\n\t\t_, err := s.ExecuteSql(ctx, &spannerpb.ExecuteSqlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx1.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tSql:        `UPDATE Simple SET Value = \"zzz\" WHERE Id = 100`,\n\t\t\tParams:     nil,\n\t\t\tParamTypes: nil,\n\t\t})\n\t\tassertStatusCode(t, err, codes.OK)\n\n\t\texpected := []*structpb.Value{\n\t\t\tmakeStringValue(\"100\"),\n\t\t\tmakeStringValue(\"zzz\"),\n\t\t}\n\n\t\t// read in another transaction timeouts\n\t\tctx2, cancel := context.WithTimeout(ctx, 100*time.Millisecond)\n\t\tdefer cancel()\n\t\t_, err = readSimple(ctx2, session, tx2)\n\t\tassertStatusCode(t, err, codes.DeadlineExceeded)\n\n\t\tif err := commit(session, tx1); err != nil {\n\t\t\tt.Fatalf(\"commit failed: %v\", err)\n\t\t}\n\n\t\t// read in another transaction succeeds after commit\n\t\tctx3, cancel := context.WithTimeout(ctx, 100*time.Millisecond)\n\t\tdefer cancel()\n\t\tresults, err := readSimple(ctx3, session, tx2)\n\t\tassertStatusCode(t, err, codes.OK)\n\t\tif diff := cmp.Diff(results, expected, protocmp.Transform()); diff != \"\" {\n\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"ReadBeforeWrite_AnotherTransaction\", func(t *testing.T) {\n\t\tsession := preareDBAndSession(t)\n\t\ttx1 := begin(session)\n\t\ttx2 := begin(session)\n\t\tdefer commit(session, tx1)\n\t\tdefer commit(session, tx2)\n\n\t\texpected := []*structpb.Value{\n\t\t\tmakeStringValue(\"100\"),\n\t\t\tmakeStringValue(\"zzz\"),\n\t\t}\n\n\t\t// read in transaction2\n\t\tresults, err := readSimple(ctx, session, tx2)\n\t\tassertStatusCode(t, err, codes.OK)\n\t\tif diff := cmp.Diff(results, initialValueSet, protocmp.Transform()); diff != \"\" {\n\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t}\n\n\t\t// write in transaction1 (aborting tx1)\n\t\t_, err = s.ExecuteSql(ctx, &spannerpb.ExecuteSqlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx1.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tSql:        `UPDATE Simple SET Value = \"zzz\" WHERE Id = 100`,\n\t\t\tParams:     nil,\n\t\t\tParamTypes: nil,\n\t\t})\n\t\tassertStatusCode(t, err, codes.OK)\n\n\t\tif err := commit(session, tx1); err != nil {\n\t\t\tt.Fatalf(\"commit failed: %v\", err)\n\t\t}\n\n\t\t// read in transaction fails because of abort\n\t\t_, err = readSimple(ctx, session, tx2)\n\t\tassertStatusCode(t, err, codes.Aborted)\n\n\t\t// read in another transaction succeeds after commit\n\t\ttx3 := begin(session)\n\t\tdefer commit(session, tx3)\n\t\tresults, err = readSimple(ctx, session, tx3)\n\t\tassertStatusCode(t, err, codes.OK)\n\t\tif diff := cmp.Diff(results, expected, protocmp.Transform()); diff != \"\" {\n\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"WriteBeforeWrite_AnotherTransaction\", func(t *testing.T) {\n\t\tsession := preareDBAndSession(t)\n\t\ttx1 := begin(session)\n\t\ttx2 := begin(session)\n\t\tdefer commit(session, tx1)\n\t\tdefer commit(session, tx2)\n\n\t\texpected1 := []*structpb.Value{\n\t\t\tmakeStringValue(\"100\"),\n\t\t\tmakeStringValue(\"xxx\"),\n\t\t\tmakeStringValue(\"101\"),\n\t\t\tmakeStringValue(\"yyy\"),\n\t\t}\n\n\t\t// write in transaction2\n\t\t_, err := s.ExecuteSql(ctx, &spannerpb.ExecuteSqlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx2.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tSql: `INSERT INTO Simple (Id, Value) VALUES(101, \"yyy\")`,\n\t\t})\n\t\tassertStatusCode(t, err, codes.OK)\n\n\t\t// write in transaction1 timeouts because tx2 holds write lock\n\t\tctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)\n\t\tdefer cancel()\n\t\t_, err = s.ExecuteSql(ctx, &spannerpb.ExecuteSqlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx1.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tSql:        `UPDATE Simple SET Value = \"zzz\" WHERE Id = 100`,\n\t\t\tParams:     nil,\n\t\t\tParamTypes: nil,\n\t\t})\n\t\tassertStatusCode(t, err, codes.DeadlineExceeded)\n\n\t\t// commit in transaction2\n\t\tif err := commit(session, tx2); err != nil {\n\t\t\tt.Fatalf(\"commit failed: %v\", err)\n\t\t}\n\n\t\t// read in another transaction succeeds after commit\n\t\ttx3 := begin(session)\n\t\tdefer commit(session, tx3)\n\t\tresults, err := readSimple(ctx, session, tx3)\n\t\tassertStatusCode(t, err, codes.OK)\n\t\tif diff := cmp.Diff(results, expected1, protocmp.Transform()); diff != \"\" {\n\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t}\n\t})\n}\n\nfunc TestExecuteBatchDml_Success(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\n\tpreareDBAndSession := func(t *testing.T) *spannerpb.Session {\n\t\tsession, dbName := testCreateSession(t, s)\n\t\tdb, ok := s.db[dbName]\n\t\tif !ok {\n\t\t\tt.Fatalf(\"database not found\")\n\t\t}\n\t\tfor _, s := range allSchema {\n\t\t\tddls := parseDDL(t, s)\n\t\t\tfor _, ddl := range ddls {\n\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t}\n\t\t}\n\n\t\tfor _, query := range []string{\n\t\t\t`INSERT INTO Simple VALUES(100, \"xxx\")`,\n\t\t\t`INSERT INTO Simple VALUES(200, \"yyy\")`,\n\t\t\t`INSERT INTO Simple VALUES(300, \"zzz\")`,\n\t\t} {\n\t\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t\t}\n\t\t}\n\n\t\treturn session\n\t}\n\tbegin := func(session *spannerpb.Session) *spannerpb.Transaction {\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\treturn tx\n\t}\n\tcommit := func(session *spannerpb.Session, tx *spannerpb.Transaction) error {\n\t\t_, err := s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t})\n\t\treturn err\n\t}\n\tread := func(session *spannerpb.Session, tx *spannerpb.Transaction) ([][]*structpb.Value, error) {\n\t\tfake := NewFakeExecuteStreamingSqlServer(ctx)\n\t\tif err := s.StreamingRead(&spannerpb.ReadRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_SingleUse{\n\t\t\t\t\tSingleUse: &spannerpb.TransactionOptions{\n\t\t\t\t\t\tMode: &spannerpb.TransactionOptions_ReadOnly_{\n\t\t\t\t\t\t\tReadOnly: &spannerpb.TransactionOptions_ReadOnly{},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tTable:   \"Simple\",\n\t\t\tColumns: []string{\"Id\", \"Value\"},\n\t\t\tKeySet:  &spannerpb.KeySet{All: true},\n\t\t}, fake); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar results [][]*structpb.Value\n\t\tfor _, set := range fake.sets {\n\t\t\tresults = append(results, set.Values)\n\t\t}\n\t\treturn results, nil\n\t}\n\n\ttable := map[string]struct {\n\t\tstmts []*spannerpb.ExecuteBatchDmlRequest_Statement\n\n\t\texpectedSetCount      int\n\t\texpectedStatusCode    int32\n\t\texpectedStatusMessage *regexp.Regexp\n\t\texpected              [][]*structpb.Value\n\t}{\n\t\t\"UpdateSingle\": {\n\t\t\tstmts: []*spannerpb.ExecuteBatchDmlRequest_Statement{\n\t\t\t\t{\n\t\t\t\t\tSql: `UPDATE Simple SET Value = \"xyz\" WHERE Id = 200`,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedSetCount:      1,\n\t\t\texpectedStatusCode:    0,\n\t\t\texpectedStatusMessage: regexp.MustCompile(`^$`),\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"100\"), makeStringValue(\"xxx\"),\n\t\t\t\t\tmakeStringValue(\"200\"), makeStringValue(\"xyz\"),\n\t\t\t\t\tmakeStringValue(\"300\"), makeStringValue(\"zzz\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"UpdateMulti\": {\n\t\t\tstmts: []*spannerpb.ExecuteBatchDmlRequest_Statement{\n\t\t\t\t{\n\t\t\t\t\tSql: `UPDATE Simple SET Value = \"xyz\" WHERE Id = 200`,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tSql: `UPDATE Simple SET Value = \"z\" WHERE Id = 300`,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedSetCount:      2,\n\t\t\texpectedStatusCode:    0,\n\t\t\texpectedStatusMessage: regexp.MustCompile(`^$`),\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"100\"), makeStringValue(\"xxx\"),\n\t\t\t\t\tmakeStringValue(\"200\"), makeStringValue(\"xyz\"),\n\t\t\t\t\tmakeStringValue(\"300\"), makeStringValue(\"z\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"PartialSuccess\": {\n\t\t\tstmts: []*spannerpb.ExecuteBatchDmlRequest_Statement{\n\t\t\t\t{\n\t\t\t\t\tSql: `UPDATE Simple SET Value = \"xyz\" WHERE Id = 200`,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tSql: `UPD`,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedSetCount:      1,\n\t\t\texpectedStatusCode:    int32(codes.InvalidArgument),\n\t\t\texpectedStatusMessage: regexp.MustCompile(`^Statement 1: .* is not valid DML`),\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"100\"), makeStringValue(\"xxx\"),\n\t\t\t\t\tmakeStringValue(\"200\"), makeStringValue(\"xyz\"),\n\t\t\t\t\tmakeStringValue(\"300\"), makeStringValue(\"zzz\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"PartialSuccessStopped\": {\n\t\t\tstmts: []*spannerpb.ExecuteBatchDmlRequest_Statement{\n\t\t\t\t{\n\t\t\t\t\tSql: `UPD`,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tSql: `UPDATE Simple SET Value = \"xyz\" WHERE Id = 200`,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedSetCount:      0,\n\t\t\texpectedStatusCode:    int32(codes.InvalidArgument),\n\t\t\texpectedStatusMessage: regexp.MustCompile(`^Statement 0: .* is not valid DML`),\n\t\t\texpected: [][]*structpb.Value{\n\t\t\t\t{\n\t\t\t\t\tmakeStringValue(\"100\"), makeStringValue(\"xxx\"),\n\t\t\t\t\tmakeStringValue(\"200\"), makeStringValue(\"yyy\"),\n\t\t\t\t\tmakeStringValue(\"300\"), makeStringValue(\"zzz\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tc := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithTimeout(ctx, 3*time.Second)\n\t\t\tdefer cancel()\n\n\t\t\tsession := preareDBAndSession(t)\n\t\t\ttx := begin(session)\n\n\t\t\tresult, err := s.ExecuteBatchDml(ctx, &spannerpb.ExecuteBatchDmlRequest{\n\t\t\t\tSession: session.Name,\n\t\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\t\tId: tx.Id,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tStatements: tc.stmts,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\tif count := len(result.ResultSets); count != tc.expectedSetCount {\n\t\t\t\tt.Errorf(\"expect the number of ResultSets to be %v, but got %v\", tc.expectedSetCount, count)\n\t\t\t}\n\n\t\t\tif result.Status.GetCode() != tc.expectedStatusCode {\n\t\t\t\tt.Errorf(\"expect status code to be %v, but got %v\", tc.expectedStatusCode, result.Status.GetCode())\n\t\t\t}\n\t\t\tif !tc.expectedStatusMessage.MatchString(result.Status.GetMessage()) {\n\t\t\t\tt.Errorf(\"unexpected status message: \\n %q\\n expected:\\n %q\", result.Status.GetMessage(), tc.expectedStatusMessage)\n\t\t\t}\n\n\t\t\tif err := commit(session, tx); err != nil {\n\t\t\t\tt.Fatalf(\"commit error: %v\", err)\n\t\t\t}\n\n\t\t\tresults, err := read(session, tx)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"read error: %v\", err)\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(tc.expected, results, protocmp.Transform()); diff != \"\" {\n\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestExecuteBatchDml_Error(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\tbegin := func(ctx context.Context, t *testing.T, session *spannerpb.Session) *spannerpb.Transaction {\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\treturn tx\n\t}\n\n\tt.Run(\"NoStatements\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx := begin(ctx, t, session)\n\t\t_, err := s.ExecuteBatchDml(ctx, &spannerpb.ExecuteBatchDmlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tStatements: []*spannerpb.ExecuteBatchDmlRequest_Statement{},\n\t\t})\n\t\texpected := \"No statements in batch DML request.\"\n\t\tassertGRPCError(t, err, codes.InvalidArgument, expected)\n\t})\n\n\tt.Run(\"RollbackAfterRollback\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx := begin(ctx, t, session)\n\n\t\t_, err := s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.ExecuteBatchDml(ctx, &spannerpb.ExecuteBatchDmlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tStatements: []*spannerpb.ExecuteBatchDmlRequest_Statement{\n\t\t\t\t{Sql: `UPDATE Simple SET Value = \"x\" WHERE Id = 1000`},\n\t\t\t},\n\t\t})\n\t\texpected := \"Cannot start a read or query within a transaction after Commit() or Rollback() has been called.\"\n\t\tassertGRPCError(t, err, codes.FailedPrecondition, expected)\n\t})\n\n\tt.Run(\"CommitAfterRollback\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx := begin(ctx, t, session)\n\n\t\t_, err := s.Rollback(ctx, &spannerpb.RollbackRequest{\n\t\t\tSession:       session.Name,\n\t\t\tTransactionId: tx.Id,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.ExecuteBatchDml(ctx, &spannerpb.ExecuteBatchDmlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tStatements: []*spannerpb.ExecuteBatchDmlRequest_Statement{\n\t\t\t\t{Sql: `UPDATE Simple SET Value = \"x\" WHERE Id = 1000`},\n\t\t\t},\n\t\t})\n\t\texpected := \"Cannot start a read or query within a transaction after Commit() or Rollback() has been called.\"\n\t\tassertGRPCError(t, err, codes.FailedPrecondition, expected)\n\t})\n\n\tt.Run(\"ReadOnlyTransaction\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadOnly_{\n\t\t\t\t\tReadOnly: &spannerpb.TransactionOptions_ReadOnly{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.ExecuteBatchDml(ctx, &spannerpb.ExecuteBatchDmlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tStatements: []*spannerpb.ExecuteBatchDmlRequest_Statement{\n\t\t\t\t{Sql: `UPDATE Simple SET Value = \"x\" WHERE Id = 1000`},\n\t\t\t},\n\t\t})\n\t\texpected := \"DML statements can only be performed in a read-write transaction.\"\n\t\tassertGRPCError(t, err, codes.FailedPrecondition, expected)\n\t})\n\n\tt.Run(\"SingleUseTransaction\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\t_, err := s.ExecuteBatchDml(ctx, &spannerpb.ExecuteBatchDmlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_SingleUse{\n\t\t\t\t\tSingleUse: &spannerpb.TransactionOptions{\n\t\t\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStatements: []*spannerpb.ExecuteBatchDmlRequest_Statement{\n\t\t\t\t{Sql: `UPDATE Simple SET Value = \"x\" WHERE Id = 1000`},\n\t\t\t},\n\t\t})\n\t\texpected := \"DML statements may not be performed in single-use transactions, to avoid replay.\"\n\t\tassertGRPCError(t, err, codes.InvalidArgument, expected)\n\t})\n}\n\nfunc TestCommit(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\n\tt.Run(\"Success_MultiUseTransaction\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"Success_SingleUse\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\t_, err := s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_SingleUseTransaction{\n\t\t\t\tSingleUseTransaction: &spannerpb.TransactionOptions{\n\t\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"CommitAfterCommit\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"CommitAfterRollback\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.Rollback(ctx, &spannerpb.RollbackRequest{\n\t\t\tSession:       session.Name,\n\t\t\tTransactionId: tx.Id,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t})\n\t\texpected := \"Cannot commit a transaction after Rollback() has been called.\"\n\t\tassertGRPCError(t, err, codes.FailedPrecondition, expected)\n\t})\n\n\tt.Run(\"ReadOnlyTransaction\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadOnly_{\n\t\t\t\t\tReadOnly: &spannerpb.TransactionOptions_ReadOnly{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t})\n\t\texpected := \"Cannot commit a read-only transaction.\"\n\t\tassertGRPCError(t, err, codes.FailedPrecondition, expected)\n\t})\n\n\tt.Run(\"SingleReadOnlyTransaction\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\t_, err := s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_SingleUseTransaction{\n\t\t\t\tSingleUseTransaction: &spannerpb.TransactionOptions{\n\t\t\t\t\tMode: &spannerpb.TransactionOptions_ReadOnly_{\n\t\t\t\t\t\tReadOnly: &spannerpb.TransactionOptions_ReadOnly{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t})\n\t\texpected := \"Cannot commit a single-use read-only transaction.\"\n\t\tassertGRPCError(t, err, codes.FailedPrecondition, expected)\n\t})\n\n\tt.Run(\"TransactionInDifferentSession\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tsession2, _ := testCreateSession(t, s)\n\n\t\t_, err = s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session2.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t})\n\t\tassertGRPCError(t, err, codes.InvalidArgument, \"Transaction was started in a different session\")\n\t})\n\n\tt.Run(\"Invalidated_Commit\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx := testInvalidatedTransaction(t, s, session)\n\n\t\t_, err := s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t})\n\t\texpected := \"This transaction has been invalidated by a later transaction in the same session.\"\n\t\tassertGRPCError(t, err, codes.FailedPrecondition, expected)\n\t})\n}\n\nfunc TestCommitMutations(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\n\tsession, dbName := testCreateSession(t, s)\n\tfor _, schema := range allSchema {\n\t\tddls := parseDDL(t, schema)\n\t\tfor _, ddl := range ddls {\n\t\t\tif err := s.ApplyDDL(ctx, dbName, ddl); err != nil {\n\t\t\t\tt.Fatalf(\"ApplyDDL failed: %v\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tsingleTx := &spannerpb.CommitRequest_SingleUseTransaction{\n\t\tSingleUseTransaction: &spannerpb.TransactionOptions{\n\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t},\n\t\t},\n\t}\n\n\t_, err := s.Commit(ctx, &spannerpb.CommitRequest{\n\t\tSession:     session.Name,\n\t\tTransaction: singleTx,\n\t\tMutations: []*spannerpb.Mutation{\n\t\t\t{\n\t\t\t\tOperation: &spannerpb.Mutation_Insert{\n\t\t\t\t\tInsert: &spannerpb.Mutation_Write{\n\t\t\t\t\t\tTable:   \"Simple\",\n\t\t\t\t\t\tColumns: []string{\"Id\", \"Value\"},\n\t\t\t\t\t\tValues: []*structpb.ListValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"aaa\"),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"bbb\"),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"300\"),\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"ccc\"),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"400\"),\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"ddd\"),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"Commit1 failed: %v\", err)\n\t}\n\n\tsingleTx2 := &spannerpb.TransactionSelector{\n\t\tSelector: &spannerpb.TransactionSelector_SingleUse{\n\t\t\tSingleUse: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfake := NewFakeExecuteStreamingSqlServer(ctx)\n\terr = s.StreamingRead(&spannerpb.ReadRequest{\n\t\tSession:     session.Name,\n\t\tTransaction: singleTx2,\n\t\tTable:       \"Simple\",\n\t\tColumns:     []string{\"Id\"},\n\t\tKeySet:      &spannerpb.KeySet{All: true},\n\t}, fake)\n\tif err != nil {\n\t\tt.Fatalf(\"Read failed: %v\", err)\n\t}\n\n\t_, err = s.Commit(ctx, &spannerpb.CommitRequest{\n\t\tSession:     session.Name,\n\t\tTransaction: singleTx,\n\t\tMutations: []*spannerpb.Mutation{\n\t\t\t{\n\t\t\t\tOperation: &spannerpb.Mutation_Insert{\n\t\t\t\t\tInsert: &spannerpb.Mutation_Write{\n\t\t\t\t\t\tTable:   \"Simple\",\n\t\t\t\t\t\tColumns: []string{\"Id\", \"Value\"},\n\t\t\t\t\t\tValues: []*structpb.ListValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"500\"),\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"eee\"),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &spannerpb.Mutation_Update{\n\t\t\t\t\tUpdate: &spannerpb.Mutation_Write{\n\t\t\t\t\t\tTable:   \"Simple\",\n\t\t\t\t\t\tColumns: []string{\"Id\", \"Value\"},\n\t\t\t\t\t\tValues: []*structpb.ListValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"aaa2\"),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &spannerpb.Mutation_Replace{\n\t\t\t\t\tReplace: &spannerpb.Mutation_Write{\n\t\t\t\t\t\tTable:   \"Simple\",\n\t\t\t\t\t\tColumns: []string{\"Id\", \"Value\"},\n\t\t\t\t\t\tValues: []*structpb.ListValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"bbb2\"),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &spannerpb.Mutation_InsertOrUpdate{\n\t\t\t\t\tInsertOrUpdate: &spannerpb.Mutation_Write{\n\t\t\t\t\t\tTable:   \"Simple\",\n\t\t\t\t\t\tColumns: []string{\"Id\", \"Value\"},\n\t\t\t\t\t\tValues: []*structpb.ListValue{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"300\"),\n\t\t\t\t\t\t\t\t\tmakeStringValue(\"ccc2\"),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &spannerpb.Mutation_Delete_{\n\t\t\t\t\tDelete: &spannerpb.Mutation_Delete{\n\t\t\t\t\t\tTable: \"Simple\",\n\t\t\t\t\t\tKeySet: &spannerpb.KeySet{\n\t\t\t\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\t\tmakeStringValue(\"400\"),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"Commit2 failed: %v\", err)\n\t}\n\n\tfake2 := NewFakeExecuteStreamingSqlServer(ctx)\n\terr = s.StreamingRead(&spannerpb.ReadRequest{\n\t\tSession:     session.Name,\n\t\tTransaction: singleTx2,\n\t\tTable:       \"Simple\",\n\t\tColumns:     []string{\"Id\", \"Value\"},\n\t\tKeySet:      &spannerpb.KeySet{All: true},\n\t}, fake2)\n\tif err != nil {\n\t\tt.Fatalf(\"Read failed: %v\", err)\n\t}\n\n\tvar results []*structpb.Value\n\tfor _, set := range fake2.sets {\n\t\tresults = append(results, set.Values...)\n\t}\n\n\texpected := []*structpb.Value{\n\t\tmakeStringValue(\"100\"),\n\t\tmakeStringValue(\"aaa2\"),\n\t\tmakeStringValue(\"200\"),\n\t\tmakeStringValue(\"bbb2\"),\n\t\tmakeStringValue(\"300\"),\n\t\tmakeStringValue(\"ccc2\"),\n\t\tmakeStringValue(\"500\"),\n\t\tmakeStringValue(\"eee\"),\n\t}\n\tif diff := cmp.Diff(expected, results, protocmp.Transform()); diff != \"\" {\n\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t}\n}\n\nfunc TestCommitMutations_AtomicOperation(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\n\tpreareDBAndSession := func(t *testing.T) *spannerpb.Session {\n\t\tsession, dbName := testCreateSession(t, s)\n\t\tdb, ok := s.db[dbName]\n\t\tif !ok {\n\t\t\tt.Fatalf(\"database not found\")\n\t\t}\n\t\tfor _, s := range allSchema {\n\t\t\tddls := parseDDL(t, s)\n\t\t\tfor _, ddl := range ddls {\n\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t}\n\t\t}\n\n\t\tfor _, query := range []string{\n\t\t\t`INSERT INTO Simple VALUES(100, \"xxx\")`,\n\t\t} {\n\t\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t\t}\n\t\t}\n\n\t\treturn session\n\t}\n\n\tsession := preareDBAndSession(t)\n\n\tbegin := func() *spannerpb.Transaction {\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\treturn tx\n\t}\n\treadSimple := func(t *testing.T) [][]*structpb.Value {\n\t\tfake := NewFakeExecuteStreamingSqlServer(ctx)\n\t\terr := s.StreamingRead(&spannerpb.ReadRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_SingleUse{\n\t\t\t\t\tSingleUse: &spannerpb.TransactionOptions{\n\t\t\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tTable:   \"Simple\",\n\t\t\tColumns: []string{\"Id\", \"Value\"},\n\t\t\tKeySet:  &spannerpb.KeySet{All: true},\n\t\t}, fake)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"StreamingRead error: %v\", err)\n\t\t}\n\n\t\tvar results [][]*structpb.Value\n\t\tfor _, set := range fake.sets {\n\t\t\tresults = append(results, set.Values)\n\t\t}\n\n\t\treturn results\n\t}\n\n\texpected := [][]*structpb.Value{\n\t\t{\n\t\t\tmakeStringValue(\"100\"),\n\t\t\tmakeStringValue(\"xxx\"),\n\t\t},\n\t}\n\n\tt.Run(\"InsertConflict\", func(t *testing.T) {\n\t\ttx := begin()\n\t\t_, err := s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{\n\t\t\t\t{\n\t\t\t\t\tOperation: &spannerpb.Mutation_Insert{\n\t\t\t\t\t\tInsert: &spannerpb.Mutation_Write{\n\t\t\t\t\t\t\tTable:   \"Simple\",\n\t\t\t\t\t\t\tColumns: []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\tValues: []*structpb.ListValue{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\t\tmakeStringValue(\"300\"),\n\t\t\t\t\t\t\t\t\t\tmakeStringValue(\"ccc2\"),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tOperation: &spannerpb.Mutation_Insert{\n\t\t\t\t\t\tInsert: &spannerpb.Mutation_Write{\n\t\t\t\t\t\t\tTable:   \"Simple\",\n\t\t\t\t\t\t\tColumns: []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\tValues: []*structpb.ListValue{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\t\tmakeStringValue(\"100\"), // already exists\n\t\t\t\t\t\t\t\t\t\tmakeStringValue(\"ccc2\"),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tassertStatusCode(t, err, codes.AlreadyExists)\n\n\t\tresults := readSimple(t)\n\t\tif diff := cmp.Diff(results, expected, protocmp.Transform()); diff != \"\" {\n\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t}\n\t})\n}\n\nfunc TestRollback(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\n\tt.Run(\"Success\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.Rollback(ctx, &spannerpb.RollbackRequest{\n\t\t\tSession:       session.Name,\n\t\t\tTransactionId: tx.Id,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"RollbackAfterRollback\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.Rollback(ctx, &spannerpb.RollbackRequest{\n\t\t\tSession:       session.Name,\n\t\t\tTransactionId: tx.Id,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.Rollback(ctx, &spannerpb.RollbackRequest{\n\t\t\tSession:       session.Name,\n\t\t\tTransactionId: tx.Id,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"RollbackAfterCommit\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\t_, err = s.Rollback(ctx, &spannerpb.RollbackRequest{\n\t\t\tSession:       session.Name,\n\t\t\tTransactionId: tx.Id,\n\t\t})\n\t\texpected := \"Cannot rollback a transaction after Commit() has been called.\"\n\t\tassertGRPCError(t, err, codes.FailedPrecondition, expected)\n\t})\n\n\tt.Run(\"TransactionInDifferentSession\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tsession2, _ := testCreateSession(t, s)\n\n\t\t_, err = s.Rollback(ctx, &spannerpb.RollbackRequest{\n\t\t\tSession:       session2.Name,\n\t\t\tTransactionId: tx.Id,\n\t\t})\n\t\tassertGRPCError(t, err, codes.InvalidArgument, \"Transaction was started in a different session\")\n\t})\n\n\tt.Run(\"InvalidatedTransaction\", func(t *testing.T) {\n\t\tsession, _ := testCreateSession(t, s)\n\t\ttx := testInvalidatedTransaction(t, s, session)\n\n\t\t_, err := s.Rollback(ctx, &spannerpb.RollbackRequest{\n\t\t\tSession:       session.Name,\n\t\t\tTransactionId: tx.Id,\n\t\t})\n\t\texpected := \"This transaction has been invalidated by a later transaction in the same session.\"\n\t\tassertGRPCError(t, err, codes.FailedPrecondition, expected)\n\t})\n}\n\nfunc TestTransaction(t *testing.T) {\n\tctx := context.Background()\n\n\ts := newTestServer()\n\n\tpreareDBAndSession := func(t *testing.T) *spannerpb.Session {\n\t\tsession, dbName := testCreateSession(t, s)\n\t\tdb, ok := s.db[dbName]\n\t\tif !ok {\n\t\t\tt.Fatalf(\"database not found\")\n\t\t}\n\t\tfor _, s := range allSchema {\n\t\t\tddls := parseDDL(t, s)\n\t\t\tfor _, ddl := range ddls {\n\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t}\n\t\t}\n\n\t\tfor _, query := range []string{\n\t\t\t`INSERT INTO Simple VALUES(100, \"xxx\")`,\n\t\t} {\n\t\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t\t}\n\t\t}\n\n\t\treturn session\n\t}\n\n\ttestcase := map[string]func(session string, tx *spannerpb.TransactionSelector, stream spannerpb.Spanner_StreamingReadServer) error{\n\t\t\"ExecuteStreamingSql\": func(session string, tx *spannerpb.TransactionSelector, stream spannerpb.Spanner_StreamingReadServer) error {\n\t\t\treturn s.ExecuteStreamingSql(&spannerpb.ExecuteSqlRequest{\n\t\t\t\tSession:     session,\n\t\t\t\tTransaction: tx,\n\t\t\t\tSql:         \"SELECT 1\",\n\t\t\t}, stream)\n\t\t},\n\t\t\"StreamingRead\": func(session string, tx *spannerpb.TransactionSelector, stream spannerpb.Spanner_StreamingReadServer) error {\n\t\t\treturn s.StreamingRead(&spannerpb.ReadRequest{\n\t\t\t\tSession:     session,\n\t\t\t\tTransaction: tx,\n\t\t\t\tTable:       \"Simple\",\n\t\t\t\tColumns:     []string{\"Id\"},\n\t\t\t\tKeySet:      &spannerpb.KeySet{All: true},\n\t\t\t}, stream)\n\t\t},\n\t\t\"ExecuteSql\": func(session string, tx *spannerpb.TransactionSelector, stream spannerpb.Spanner_StreamingReadServer) error {\n\t\t\tr, err := s.ExecuteSql(context.Background(), &spannerpb.ExecuteSqlRequest{\n\t\t\t\tSession:     session,\n\t\t\t\tTransaction: tx,\n\t\t\t\tSql:         `UPDATE Simple SET Value = \"xxx\" WHERE Id = 10000000`,\n\t\t\t})\n\t\t\tif err == nil {\n\t\t\t\t// set data with stream to make test easir\n\t\t\t\tstream.Send(&spannerpb.PartialResultSet{\n\t\t\t\t\tMetadata: r.Metadata,\n\t\t\t\t\tStats:    r.Stats,\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn err\n\t\t},\n\t\t\"ExecuteBatchDml\": func(session string, tx *spannerpb.TransactionSelector, stream spannerpb.Spanner_StreamingReadServer) error {\n\t\t\tr, err := s.ExecuteBatchDml(context.Background(), &spannerpb.ExecuteBatchDmlRequest{\n\t\t\t\tSession:     session,\n\t\t\t\tTransaction: tx,\n\t\t\t\tStatements: []*spannerpb.ExecuteBatchDmlRequest_Statement{\n\t\t\t\t\t{Sql: `UPDATE Simple SET Value = \"xxx\" WHERE Id = 10000000`},\n\t\t\t\t},\n\t\t\t})\n\t\t\tif err == nil {\n\t\t\t\t// set data with stream to make test easir\n\t\t\t\tfor i := range r.ResultSets {\n\t\t\t\t\tstream.Send(&spannerpb.PartialResultSet{\n\t\t\t\t\t\tMetadata: r.ResultSets[i].Metadata,\n\t\t\t\t\t\tStats:    r.ResultSets[i].Stats,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn err\n\t\t},\n\t}\n\n\tfor name, fn := range testcase {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tt.Run(\"UseReturnedTransaction\", func(t *testing.T) {\n\t\t\t\tsession := preareDBAndSession(t)\n\n\t\t\t\tfake := &fakeExecuteStreamingSqlServer{}\n\t\t\t\tbegin := &spannerpb.TransactionSelector{\n\t\t\t\t\tSelector: &spannerpb.TransactionSelector_Begin{\n\t\t\t\t\t\tBegin: &spannerpb.TransactionOptions{\n\t\t\t\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tif err := fn(session.Name, begin, fake); err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t\t}\n\t\t\t\tnewtx := fake.sets[0].GetMetadata().GetTransaction()\n\t\t\t\tif newtx == nil {\n\t\t\t\t\tt.Fatalf(\"transaction should not be nil\")\n\t\t\t\t}\n\n\t\t\t\tfake2 := &fakeExecuteStreamingSqlServer{}\n\t\t\t\ttxsel := &spannerpb.TransactionSelector{\n\t\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\t\tId: newtx.Id,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tif err := fn(session.Name, txsel, fake2); err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tt.Run(\"AfterCommit\", func(t *testing.T) {\n\t\t\t\tsession, _ := testCreateSession(t, s)\n\n\t\t\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\t\t\tSession: session.Name,\n\t\t\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t\t}\n\n\t\t\t\t_, err = s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\t\t\tSession: session.Name,\n\t\t\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\t\t\tTransactionId: tx.Id,\n\t\t\t\t\t},\n\t\t\t\t\tMutations: []*spannerpb.Mutation{},\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tfake := &fakeExecuteStreamingSqlServer{}\n\t\t\t\ttxsel := &spannerpb.TransactionSelector{\n\t\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\t\tId: tx.Id,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\terr = fn(session.Name, txsel, fake)\n\t\t\t\texpected := \"Cannot start a read or query within a transaction after Commit() or Rollback() has been called.\"\n\t\t\t\tassertGRPCError(t, err, codes.FailedPrecondition, expected)\n\t\t\t})\n\n\t\t\tt.Run(\"InvalidatedTransaction\", func(t *testing.T) {\n\t\t\t\tsession, _ := testCreateSession(t, s)\n\t\t\t\ttx := testInvalidatedTransaction(t, s, session)\n\n\t\t\t\tfake := &fakeExecuteStreamingSqlServer{}\n\t\t\t\ttxsel := &spannerpb.TransactionSelector{\n\t\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\t\tId: tx.Id,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\terr := fn(session.Name, txsel, fake)\n\t\t\t\texpected := \"This transaction has been invalidated by a later transaction in the same session.\"\n\t\t\t\tassertGRPCError(t, err, codes.FailedPrecondition, expected)\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestUpdateDatabaseDdl(t *testing.T) {\n\tctx := context.Background()\n\ts := newTestServer()\n\t_, dbName := testCreateSession(t, s)\n\n\tstmts := []string{`CREATE TABLE Test (Id INT64) PRIMARY KEY(Id)`}\n\top, err := s.UpdateDatabaseDdl(ctx, &adminv1pb.UpdateDatabaseDdlRequest{\n\t\tDatabase:   dbName,\n\t\tStatements: stmts,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\top2, err := s.WaitOperation(ctx, &lropb.WaitOperationRequest{\n\t\tName: op.Name,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif !op2.Done {\n\t\tt.Errorf(\"should be success\")\n\t}\n}\n\nfunc TestCreateDatabase(t *testing.T) {\n\tctx := context.Background()\n\ts := newTestServer()\n\n\tprojectID, instanceID := \"fake\", \"fake\"\n\tparent := fmt.Sprintf(\"projects/%s/instances/%s\", projectID, instanceID)\n\textraStmts := []string{`\nCREATE TABLE Ids (\n  Id INT64 NOT NULL,\n) PRIMARY KEY(Id)`}\n\n\talreadyExistsDatabase := \"already-exists\"\n\ts.createDatabase(fmt.Sprintf(\"%s/databases/%s\", parent, alreadyExistsDatabase))\n\n\tt.Run(\"Success\", func(t *testing.T) {\n\t\tdatabaseID := strconv.Itoa(int(time.Now().Unix()))\n\t\top, err := s.CreateDatabase(ctx, &adminv1pb.CreateDatabaseRequest{\n\t\t\tParent:          parent,\n\t\t\tCreateStatement: fmt.Sprintf(\"CREATE DATABASE `%s`\", databaseID),\n\t\t\tExtraStatements: extraStmts,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to create database: %s\", err)\n\t\t}\n\t\tif _, ok := s.db[fmt.Sprintf(\"%s/databases/%s\", parent, databaseID)]; !ok {\n\t\t\tt.Fatal(\"failed to get database from map\")\n\t\t}\n\t\tif !op.GetDone() {\n\t\t\tt.Fatal(\"the operation has not finished yet\")\n\t\t}\n\t\tvar db adminv1pb.Database\n\t\tif err := anypb.UnmarshalTo(op.GetResponse(), &db, proto.UnmarshalOptions{DiscardUnknown: true}); err != nil {\n\t\t\tt.Fatalf(\"failed to unmarshal response: %s\", err)\n\t\t}\n\t\tif got, expect := db.GetName(), fmt.Sprintf(\"%s/databases/%s\", parent, databaseID); got != expect {\n\t\t\tt.Fatalf(\"expected %s but got %s\", expect, db.GetName())\n\t\t}\n\t})\n\n\tt.Run(\"Failure\", func(t *testing.T) {\n\t\ttests := map[string]struct {\n\t\t\treq        *adminv1pb.CreateDatabaseRequest\n\t\t\texpectCode codes.Code\n\t\t}{\n\t\t\t\"invalid parent\": {\n\t\t\t\treq: &adminv1pb.CreateDatabaseRequest{\n\t\t\t\t\tParent:          \"INVALID\",\n\t\t\t\t\tCreateStatement: fmt.Sprintf(\"CREATE DATABASE `%d`\", time.Now().Unix()),\n\t\t\t\t\tExtraStatements: extraStmts,\n\t\t\t\t},\n\t\t\t\texpectCode: codes.InvalidArgument,\n\t\t\t},\n\t\t\t\"invalid create statement\": {\n\t\t\t\treq: &adminv1pb.CreateDatabaseRequest{\n\t\t\t\t\tParent:          parent,\n\t\t\t\t\tCreateStatement: \"INVALID\",\n\t\t\t\t\tExtraStatements: extraStmts,\n\t\t\t\t},\n\t\t\t\texpectCode: codes.InvalidArgument,\n\t\t\t},\n\t\t\t\"create statement is not CREATE DATABASE\": {\n\t\t\t\treq: &adminv1pb.CreateDatabaseRequest{\n\t\t\t\t\tParent:          parent,\n\t\t\t\t\tCreateStatement: extraStmts[0],\n\t\t\t\t\tExtraStatements: extraStmts,\n\t\t\t\t},\n\t\t\t\texpectCode: codes.InvalidArgument,\n\t\t\t},\n\t\t\t\"invalid extra statements\": {\n\t\t\t\treq: &adminv1pb.CreateDatabaseRequest{\n\t\t\t\t\tParent:          parent,\n\t\t\t\t\tCreateStatement: fmt.Sprintf(\"CREATE DATABASE `%d`\", time.Now().Unix()),\n\t\t\t\t\tExtraStatements: []string{\"INVALID\"},\n\t\t\t\t},\n\t\t\t\texpectCode: codes.InvalidArgument,\n\t\t\t},\n\t\t\t\"already exists\": {\n\t\t\t\treq: &adminv1pb.CreateDatabaseRequest{\n\t\t\t\t\tParent:          parent,\n\t\t\t\t\tCreateStatement: fmt.Sprintf(\"CREATE DATABASE `%s`\", alreadyExistsDatabase),\n\t\t\t\t\tExtraStatements: extraStmts,\n\t\t\t\t},\n\t\t\t\texpectCode: codes.AlreadyExists,\n\t\t\t},\n\t\t}\n\t\tfor name, test := range tests {\n\t\t\ttest := test\n\t\t\tt.Run(name, func(t *testing.T) {\n\t\t\t\t_, err := s.CreateDatabase(ctx, test.req)\n\t\t\t\tif got, expect := status.Convert(err).Code(), test.expectCode; got != expect {\n\t\t\t\t\tt.Errorf(\"expect error code %s but got %s\", expect, got)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestDropDatabase(t *testing.T) {\n\tctx := context.Background()\n\ts := newTestServer()\n\n\tdatabase := \"projects/fake/instances/fake/databases/fake\"\n\n\tt.Run(\"Success\", func(t *testing.T) {\n\t\t_, err := s.createDatabase(database)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to create database: %s\", err)\n\t\t}\n\n\t\tif _, err := s.DropDatabase(ctx, &adminv1pb.DropDatabaseRequest{\n\t\t\tDatabase: database,\n\t\t}); err != nil {\n\t\t\tt.Fatalf(\"failed to drop database: %s\", err)\n\t\t}\n\n\t\t_, err = s.GetDatabase(ctx, &adminv1pb.GetDatabaseRequest{\n\t\t\tName: database,\n\t\t})\n\t\tst := status.Convert(err)\n\t\tif st.Code() != codes.NotFound {\n\t\t\tt.Fatalf(\"failed to drop database: %s\", err)\n\t\t}\n\t\texpectedRI := &errdetails.ResourceInfo{\n\t\t\tResourceType: \"type.googleapis.com/google.spanner.admin.database.v1.Database\",\n\t\t\tResourceName: database,\n\t\t\tDescription:  \"Database does not exist.\",\n\t\t}\n\t\tassertResourceInfo(t, err, expectedRI)\n\n\t\t// DropDatabase returns no error even if the database is not exist\n\t\tif _, err := s.DropDatabase(ctx, &adminv1pb.DropDatabaseRequest{\n\t\t\tDatabase: database,\n\t\t}); err != nil {\n\t\t\tt.Fatalf(\"failed to drop database: %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"Failure\", func(t *testing.T) {\n\t\ttests := map[string]struct {\n\t\t\treq        *adminv1pb.DropDatabaseRequest\n\t\t\texpectCode codes.Code\n\t\t}{\n\t\t\t\"invalid database\": {\n\t\t\t\treq: &adminv1pb.DropDatabaseRequest{\n\t\t\t\t\tDatabase: \"INVALID\",\n\t\t\t\t},\n\t\t\t\texpectCode: codes.InvalidArgument,\n\t\t\t},\n\t\t}\n\t\tfor name, test := range tests {\n\t\t\ttest := test\n\t\t\tt.Run(name, func(t *testing.T) {\n\t\t\t\t_, err := s.DropDatabase(ctx, test.req)\n\t\t\t\tif got, expect := status.Convert(err).Code(), test.expectCode; got != expect {\n\t\t\t\t\tt.Errorf(\"expect error code %s but got %s\", expect, got)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestListDatabases(t *testing.T) {\n\tctx := context.Background()\n\ts := newTestServer()\n\n\tdatabases := []string{\n\t\t\"projects/fake/instances/fake/databases/fake\",\n\t\t\"projects/fake/instances/fake/databases/fake2\",\n\t\t\"projects/fake/instances/fake/databases/fake3\",\n\t}\n\n\tfor _, name := range databases {\n\t\t_, err := s.createDatabase(name)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to create database: %s\", err)\n\t\t}\n\t}\n\n\tres, err := s.ListDatabases(ctx, &adminv1pb.ListDatabasesRequest{\n\t\tParent: \"projects/fake/instances/fake\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to list databases: %s\", err)\n\t}\n\n\tsort.Slice(res.Databases, func(i, j int) bool {\n\t\treturn res.Databases[i].Name < res.Databases[j].Name\n\t})\n\n\texpected := []*adminv1pb.Database{\n\t\t{\n\t\t\tName:  \"projects/fake/instances/fake/databases/fake\",\n\t\t\tState: adminv1pb.Database_READY,\n\t\t},\n\t\t{\n\t\t\tName:  \"projects/fake/instances/fake/databases/fake2\",\n\t\t\tState: adminv1pb.Database_READY,\n\t\t},\n\t\t{\n\t\t\tName:  \"projects/fake/instances/fake/databases/fake3\",\n\t\t\tState: adminv1pb.Database_READY,\n\t\t},\n\t}\n\tif diff := cmp.Diff(expected, res.Databases, protocmp.Transform()); diff != \"\" {\n\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t}\n}\n"
  },
  {
    "path": "server/server_transaction_test.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\tspannerpb \"google.golang.org/genproto/googleapis/spanner/v1\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tstructpb \"google.golang.org/protobuf/types/known/structpb\"\n)\n\nfunc TestReadAndWriteTransaction_Aborted(t *testing.T) {\n\tif testing.Short() {\n\t\tt.Skip(\"skip long test\")\n\t}\n\n\tctx := context.Background()\n\n\ts := newTestServer()\n\n\tpreareDBAndSession := func(t *testing.T) *spannerpb.Session {\n\t\tsession, dbName := testCreateSession(t, s)\n\t\tdb, ok := s.db[dbName]\n\t\tif !ok {\n\t\t\tt.Fatalf(\"database not found\")\n\t\t}\n\t\tfor _, s := range allSchema {\n\t\t\tddls := parseDDL(t, s)\n\t\t\tfor _, ddl := range ddls {\n\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t}\n\t\t}\n\n\t\tfor _, query := range []string{\n\t\t\t`INSERT INTO Simple VALUES(100, \"xxx\")`,\n\t\t} {\n\t\t\tif _, err := db.db.ExecContext(ctx, query); err != nil {\n\t\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t\t}\n\t\t}\n\n\t\treturn session\n\t}\n\n\tsession := preareDBAndSession(t)\n\n\tbegin := func() *spannerpb.Transaction {\n\t\ttx, err := s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\treturn tx\n\t}\n\trollback := func(tx *spannerpb.Transaction) {\n\t\t_, _ = s.Rollback(ctx, &spannerpb.RollbackRequest{\n\t\t\tSession:       session.Name,\n\t\t\tTransactionId: tx.Id,\n\t\t})\n\t\t// ignore error\n\t}\n\treadSimple := func(tx *spannerpb.Transaction) error {\n\t\tfake := NewFakeExecuteStreamingSqlServer(ctx)\n\t\terr := s.StreamingRead(&spannerpb.ReadRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tTable:   \"Simple\",\n\t\t\tColumns: []string{\"Id\"},\n\t\t\tKeySet:  &spannerpb.KeySet{All: true},\n\t\t}, fake)\n\t\treturn err\n\t}\n\treadFT := func(tx *spannerpb.Transaction) error {\n\t\tfake := NewFakeExecuteStreamingSqlServer(ctx)\n\t\terr := s.StreamingRead(&spannerpb.ReadRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tTable:   \"FullTypes\",\n\t\t\tColumns: []string{\"PKey\"},\n\t\t\tKeySet:  &spannerpb.KeySet{All: true},\n\t\t}, fake)\n\t\treturn err\n\t}\n\tquerySimple := func(tx *spannerpb.Transaction) error {\n\t\tfake := NewFakeExecuteStreamingSqlServer(ctx)\n\t\terr := s.ExecuteStreamingSql(&spannerpb.ExecuteSqlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tSql: `SELECT Id FROM Simple`,\n\t\t}, fake)\n\t\treturn err\n\t}\n\tqueryFT := func(tx *spannerpb.Transaction) error {\n\t\tfake := NewFakeExecuteStreamingSqlServer(ctx)\n\t\terr := s.ExecuteStreamingSql(&spannerpb.ExecuteSqlRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx.Id,\n\t\t\t\t},\n\t\t\t},\n\t\t\tSql: `SELECT PKey FROM FullTypes`,\n\t\t}, fake)\n\t\treturn err\n\t}\n\tcommitSimple := func(tx *spannerpb.Transaction) error {\n\t\t_, err := s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{\n\t\t\t\t{\n\t\t\t\t\tOperation: &spannerpb.Mutation_InsertOrUpdate{\n\t\t\t\t\t\tInsertOrUpdate: &spannerpb.Mutation_Write{\n\t\t\t\t\t\t\tTable:   \"Simple\",\n\t\t\t\t\t\t\tColumns: []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\tValues: []*structpb.ListValue{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\t\tmakeStringValue(\"300\"),\n\t\t\t\t\t\t\t\t\t\tmakeStringValue(\"ccc2\"),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\treturn err\n\t}\n\tcommitFT := func(tx *spannerpb.Transaction) error {\n\t\t_, err := s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: []*spannerpb.Mutation{\n\t\t\t\t{\n\t\t\t\t\tOperation: &spannerpb.Mutation_InsertOrUpdate{\n\t\t\t\t\t\tInsertOrUpdate: &spannerpb.Mutation_Write{\n\t\t\t\t\t\t\tTable:   \"FullTypes\",\n\t\t\t\t\t\t\tColumns: fullTypesKeys,\n\t\t\t\t\t\t\tValues: []*structpb.ListValue{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\t\tmakeStringValue(\"xxx\"),  // PKey STRING(32) NOT NULL,\n\t\t\t\t\t\t\t\t\t\tmakeStringValue(\"xxx\"),  // FTString STRING(32) NOT NULL,\n\t\t\t\t\t\t\t\t\t\tmakeNullValue(),         // FTStringNull STRING(32),\n\t\t\t\t\t\t\t\t\t\tmakeBoolValue(true),     // FTBool BOOL NOT NULL,\n\t\t\t\t\t\t\t\t\t\tmakeNullValue(),         // FTBoolNull BOOL,\n\t\t\t\t\t\t\t\t\t\tmakeStringValue(\"eHh4\"), // FTBytes BYTES(32) NOT NULL,\n\t\t\t\t\t\t\t\t\t\tmakeNullValue(),         // FTBytesNull BYTES(32),\n\t\t\t\t\t\t\t\t\t\tmakeStringValue(\"2012-03-04T12:34:56.123456789Z\"), // FTTimestamp TIMESTAMP NOT NULL,\n\t\t\t\t\t\t\t\t\t\tmakeNullValue(),               // FTTimestampNull TIMESTAMP,\n\t\t\t\t\t\t\t\t\t\tmakeStringValue(\"100\"),        // FTInt INT64 NOT NULL,\n\t\t\t\t\t\t\t\t\t\tmakeNullValue(),               // FTIntNull INT64,\n\t\t\t\t\t\t\t\t\t\tmakeNumberValue(0.5),          // FTFloat FLOAT64 NOT NULL,\n\t\t\t\t\t\t\t\t\t\tmakeNullValue(),               // FTFloatNull FLOAT64,\n\t\t\t\t\t\t\t\t\t\tmakeStringValue(\"2012-03-04\"), // FTDate DATE NOT NULL,\n\t\t\t\t\t\t\t\t\t\tmakeNullValue(),               // FTDateNull DATE,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\treturn err\n\t}\n\n\treadPattern := []struct {\n\t\tname       string\n\t\treadSimple func(tx *spannerpb.Transaction) error\n\t\treadFT     func(tx *spannerpb.Transaction) error\n\t}{\n\t\t{\"Read\", readSimple, readFT},\n\t\t{\"Query\", querySimple, queryFT},\n\t}\n\n\tt.Run(\"ReadLockAborted\", func(t *testing.T) {\n\t\tfor _, tc := range readPattern {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\ttx1 := begin()\n\t\t\t\ttx2 := begin()\n\t\t\t\tdefer rollback(tx1)\n\t\t\t\tdefer rollback(tx2)\n\t\t\t\tassertStatusCode(t, tc.readSimple(tx1), codes.OK)\n\t\t\t\tassertStatusCode(t, commitSimple(tx2), codes.OK)\n\t\t\t\tassertStatusCode(t, tc.readSimple(tx1), codes.Aborted)\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"ReadLock_DifferentTable\", func(t *testing.T) {\n\t\tfor _, tc := range readPattern {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\ttx1 := begin()\n\t\t\t\ttx2 := begin()\n\t\t\t\ttx3 := begin()\n\t\t\t\tdefer rollback(tx1)\n\t\t\t\tdefer rollback(tx2)\n\t\t\t\tdefer rollback(tx3)\n\t\t\t\tassertStatusCode(t, tc.readSimple(tx1), codes.OK)\n\t\t\t\tassertStatusCode(t, tc.readFT(tx2), codes.OK)\n\t\t\t\tassertStatusCode(t, commitSimple(tx3), codes.OK)\n\t\t\t\tassertStatusCode(t, tc.readSimple(tx1), codes.Aborted)\n\t\t\t\tassertStatusCode(t, tc.readFT(tx2), codes.OK)\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"WriteTest\", func(t *testing.T) {\n\t\tfor _, tc := range readPattern {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\ttx1 := begin()\n\t\t\t\ttx2 := begin()\n\t\t\t\tdefer rollback(tx1)\n\t\t\t\tdefer rollback(tx2)\n\t\t\t\tassertStatusCode(t, tc.readSimple(tx1), codes.OK)\n\t\t\t\tassertStatusCode(t, tc.readFT(tx2), codes.OK)\n\t\t\t\tassertStatusCode(t, commitSimple(tx1), codes.OK)\n\t\t\t\tassertStatusCode(t, commitFT(tx2), codes.OK)\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"ReadAbortedWhileReading\", func(t *testing.T) {\n\t\tfor _, tc := range readPattern {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\tfor i := 0; i < 100; i++ {\n\t\t\t\t\ttx1 := begin()\n\t\t\t\t\ttx2 := begin()\n\t\t\t\t\tdone := make(chan struct{})\n\t\t\t\t\terrCh := make(chan error, 2)\n\n\t\t\t\t\tassertStatusCode(t, tc.readSimple(tx1), codes.OK) // read lock first\n\t\t\t\t\tgo func() {\n\t\t\t\t\t\tfor {\n\t\t\t\t\t\t\ttime.Sleep(100 * time.Microsecond)\n\n\t\t\t\t\t\t\t// try to happen aborted while reading\n\t\t\t\t\t\t\terr := tc.readSimple(tx1)\n\t\t\t\t\t\t\tcode := status.Code(err)\n\t\t\t\t\t\t\tif code == codes.OK {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif code == codes.Aborted {\n\t\t\t\t\t\t\t\tclose(done)\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\terrCh <- err\n\t\t\t\t\t\t\tclose(done)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}()\n\t\t\t\t\ttime.Sleep(time.Duration(i/10+1) * time.Millisecond)\n\t\t\t\t\tassertStatusCode(t, commitSimple(tx2), codes.OK)\n\t\t\t\t\t<-done\n\t\t\t\t\tselect {\n\t\t\t\t\tcase err := <-errCh:\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Fatalf(\"error: %v\", err)\n\t\t\t\t\t\t}\n\t\t\t\t\tdefault:\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestReadAndWriteTransaction_AtomicUpdate(t *testing.T) {\n\tif testing.Short() {\n\t\tt.Skip(\"skip long test\")\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\ts := newTestServer()\n\n\tpreareDB := func(t *testing.T) string {\n\t\t_, dbName := testCreateSession(t, s)\n\t\tdb, ok := s.db[dbName]\n\t\tif !ok {\n\t\t\tt.Fatalf(\"database not found\")\n\t\t}\n\t\tfor _, s := range allSchema {\n\t\t\tddls := parseDDL(t, s)\n\t\t\tfor _, ddl := range ddls {\n\t\t\t\tdb.ApplyDDL(ctx, ddl)\n\t\t\t}\n\t\t}\n\n\t\treturn dbName\n\t}\n\n\tcreateSession := func(t *testing.T, dbName string) *spannerpb.Session {\n\t\tsession, err := s.CreateSession(context.Background(), &spannerpb.CreateSessionRequest{\n\t\t\tDatabase: dbName,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to create session: %v\", err)\n\t\t}\n\t\treturn session\n\t}\n\n\tbegin := func(session *spannerpb.Session) (*spannerpb.Transaction, error) {\n\t\treturn s.BeginTransaction(ctx, &spannerpb.BeginTransactionRequest{\n\t\t\tSession: session.Name,\n\t\t\tOptions: &spannerpb.TransactionOptions{\n\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t}\n\tread := func(session *spannerpb.Session, tx *spannerpb.Transaction, pKey string, sql bool) (int, error) {\n\t\tvar txSel *spannerpb.TransactionSelector\n\t\tif tx == nil {\n\t\t\ttxSel = &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_SingleUse{\n\t\t\t\t\tSingleUse: &spannerpb.TransactionOptions{\n\t\t\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t} else {\n\t\t\ttxSel = &spannerpb.TransactionSelector{\n\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\tId: tx.Id,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\tfake := NewFakeExecuteStreamingSqlServer(ctx)\n\t\tif sql {\n\t\t\terr := s.ExecuteStreamingSql(&spannerpb.ExecuteSqlRequest{\n\t\t\t\tSession:     session.Name,\n\t\t\t\tTransaction: txSel,\n\t\t\t\tSql:         `SELECT Value FROM Simple WHERE Id = @key`,\n\t\t\t\tParamTypes: map[string]*spannerpb.Type{\n\t\t\t\t\t\"key\": &spannerpb.Type{\n\t\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tParams: &structpb.Struct{\n\t\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\t\"key\": makeStringValue(pKey),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}, fake)\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t} else {\n\t\t\terr := s.StreamingRead(&spannerpb.ReadRequest{\n\t\t\t\tSession:     session.Name,\n\t\t\t\tTransaction: txSel,\n\t\t\t\tTable:       \"Simple\",\n\t\t\t\tColumns:     []string{\"Value\"},\n\t\t\t\tKeySet: &spannerpb.KeySet{\n\t\t\t\t\tKeys: []*structpb.ListValue{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\tmakeStringValue(pKey),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}, fake)\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t}\n\n\t\tvar results [][]*structpb.Value\n\t\tfor _, set := range fake.sets {\n\t\t\tresults = append(results, set.Values)\n\t\t}\n\n\t\tif len(results) != 1 {\n\t\t\treturn 0, fmt.Errorf(\"results should be 1 record but got %v\", len(results))\n\t\t}\n\n\t\tv := results[0][0].Kind.(*structpb.Value_StringValue).StringValue\n\t\tn, _ := strconv.Atoi(v)\n\t\treturn n, nil\n\t}\n\n\tcommit := func(session *spannerpb.Session, tx *spannerpb.Transaction, pKey string, sql bool, next int) error {\n\t\tvar mus []*spannerpb.Mutation\n\t\tif sql {\n\t\t\t_, err := s.ExecuteSql(ctx, &spannerpb.ExecuteSqlRequest{\n\t\t\t\tSession: session.Name,\n\t\t\t\tTransaction: &spannerpb.TransactionSelector{\n\t\t\t\t\tSelector: &spannerpb.TransactionSelector_Id{\n\t\t\t\t\t\tId: tx.Id,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSql: fmt.Sprintf(`UPDATE Simple Set Value = \"%d\" WHERE Id = %s`, next, pKey),\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\tmus = []*spannerpb.Mutation{\n\t\t\t\t{\n\t\t\t\t\tOperation: &spannerpb.Mutation_Update{\n\t\t\t\t\t\tUpdate: &spannerpb.Mutation_Write{\n\t\t\t\t\t\t\tTable:   \"Simple\",\n\t\t\t\t\t\t\tColumns: []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\tValues: []*structpb.ListValue{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\t\tmakeStringValue(pKey),\n\t\t\t\t\t\t\t\t\t\tmakeStringValue(fmt.Sprint(next)),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\t_, err := s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\tSession: session.Name,\n\t\t\tTransaction: &spannerpb.CommitRequest_TransactionId{\n\t\t\t\tTransactionId: tx.Id,\n\t\t\t},\n\t\t\tMutations: mus,\n\t\t})\n\n\t\treturn err\n\t}\n\n\tdbname := preareDB(t)\n\tsession := createSession(t, dbname)\n\n\tinitialValue := 100\n\n\ttable := []struct {\n\t\tpKey        string\n\t\tconcurrency int // should be under 32\n\t\ttries       int\n\t}{\n\t\t{pKey: \"1000\", concurrency: 5, tries: 3},\n\t\t{pKey: \"1001\", concurrency: 10, tries: 10},\n\t\t{pKey: \"1002\", concurrency: 20, tries: 10},\n\t\t{pKey: \"1003\", concurrency: 50, tries: 5},\n\t\t// {pKey: \"1004\", concurrency: 100, tries: 30},\n\t}\n\n\tfor _, tc := range table {\n\t\tpKey := fmt.Sprint(tc.pKey)\n\t\tconcurrency := tc.concurrency\n\t\ttries := tc.tries\n\t\tt.Run(fmt.Sprintf(\"KEY%s_Con%d_Try%d\", pKey, concurrency, tries), func(t *testing.T) {\n\t\t\t_, err := s.Commit(ctx, &spannerpb.CommitRequest{\n\t\t\t\tSession: session.Name,\n\t\t\t\tTransaction: &spannerpb.CommitRequest_SingleUseTransaction{\n\t\t\t\t\tSingleUseTransaction: &spannerpb.TransactionOptions{\n\t\t\t\t\t\tMode: &spannerpb.TransactionOptions_ReadWrite_{\n\t\t\t\t\t\t\tReadWrite: &spannerpb.TransactionOptions_ReadWrite{},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tMutations: []*spannerpb.Mutation{\n\t\t\t\t\t{\n\t\t\t\t\t\tOperation: &spannerpb.Mutation_Insert{\n\t\t\t\t\t\t\tInsert: &spannerpb.Mutation_Write{\n\t\t\t\t\t\t\t\tTable:   \"Simple\",\n\t\t\t\t\t\t\t\tColumns: []string{\"Id\", \"Value\"},\n\t\t\t\t\t\t\t\tValues: []*structpb.ListValue{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tValues: []*structpb.Value{\n\t\t\t\t\t\t\t\t\t\t\tmakeStringValue(pKey),\n\t\t\t\t\t\t\t\t\t\t\tmakeStringValue(fmt.Sprint(initialValue)),\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\terrCh := make(chan error, concurrency*tries)\n\t\t\twg := &sync.WaitGroup{}\n\t\t\treaderDone := make(chan struct{})\n\t\t\tfor i := 0; i < concurrency; i++ {\n\t\t\t\twg.Add(1)\n\t\t\t\tgo func(me int) {\n\t\t\t\t\tdefer wg.Done()\n\n\t\t\t\t\t// create own session\n\t\t\t\t\tsession := createSession(t, dbname)\n\n\t\t\t\t\terr := func() error {\n\t\t\t\t\t\ttry := 0\n\t\t\t\t\t\tfor {\n\t\t\t\t\t\t\tselect {\n\t\t\t\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\t\t\t\treturn ctx.Err()\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// begin transaction\n\t\t\t\t\t\t\ttx, err := begin(session)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\treturn fmt.Errorf(\"begin: %v\", err)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// read the current value\n\t\t\t\t\t\t\tuseSql := (me % 2) == 0\n\t\t\t\t\t\t\tn, err := read(session, tx, pKey, useSql)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tif code := status.Code(err); code == codes.Aborted {\n\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn fmt.Errorf(\"[%s] read: %v\", string(tx.Id), err)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnext := n + 1\n\n\t\t\t\t\t\t\t// write +1 value\n\t\t\t\t\t\t\tif err := commit(session, tx, pKey, useSql, next); err != nil {\n\t\t\t\t\t\t\t\tif code := status.Code(err); code == codes.Aborted {\n\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn fmt.Errorf(\"[%s] commit: %v\", string(tx.Id), err)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\ttry++\n\t\t\t\t\t\t\tif try == tries {\n\t\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\terrCh <- err\n\t\t\t\t\t\tcancel()\n\t\t\t\t\t}\n\t\t\t\t}(i)\n\t\t\t}\n\n\t\t\tgo func() {\n\t\t\t\t// create own session\n\t\t\t\tsession := createSession(t, dbname)\n\t\t\treadLoop:\n\t\t\t\tfor {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase <-readerDone:\n\t\t\t\t\t\tbreak readLoop\n\t\t\t\t\tdefault:\n\t\t\t\t\t}\n\t\t\t\t\tif _, err := read(session, nil, pKey, false); err != nil {\n\t\t\t\t\t\tif code := status.Code(err); code == codes.Aborted {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\terrCh <- err\n\t\t\t\t\t\tcancel()\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\twg.Wait()\n\n\t\t\tclose(readerDone)\n\n\t\t\tvar errs []error\n\t\tL:\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase err := <-errCh:\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\terrs = append(errs, err)\n\t\t\t\t\t}\n\t\t\t\t\tcancel()\n\t\t\t\tdefault:\n\t\t\t\t\tbreak L\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(errs) != 0 {\n\t\t\t\tfor _, err := range errs {\n\t\t\t\t\tt.Errorf(\"error %v\", err)\n\t\t\t\t}\n\t\t\t\tt.FailNow()\n\t\t\t}\n\n\t\t\tn, err := read(session, nil, pKey, false)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"read error %v\", err)\n\t\t\t}\n\n\t\t\texpected := initialValue + tries*concurrency\n\t\t\tif n != expected {\n\t\t\t\tt.Errorf(\"expect n to be %v, but got %v\", expected, n)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/session.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tuuidpkg \"github.com/google/uuid\"\n\tspannerpb \"google.golang.org/genproto/googleapis/spanner/v1\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/types/known/timestamppb\"\n)\n\nfunc validateSessionName(sessionName string) bool {\n\tparts := strings.Split(sessionName, \"/\")\n\tif len(parts) != 8 {\n\t\treturn false\n\t}\n\tif parts[0] != \"projects\" {\n\t\treturn false\n\t}\n\tif parts[2] != \"instances\" {\n\t\treturn false\n\t}\n\tif parts[4] != \"databases\" {\n\t\treturn false\n\t}\n\tif parts[6] != \"sessions\" {\n\t\treturn false\n\t}\n\tif parts[1] == \"\" || parts[3] == \"\" || parts[5] == \"\" || parts[7] == \"\" {\n\t\treturn false\n\t}\n\treturn true\n}\n\ntype session struct {\n\tid        string\n\tdbName    string\n\tdatabase  *database\n\tname      string\n\tcreatedAt time.Time\n\tlastUse   time.Time\n\n\tmu           sync.Mutex\n\ttransactions map[string]*transaction\n\tactiveRWTx   []*transaction\n}\n\nfunc (s *session) Name() string {\n\treturn s.name\n}\n\nfunc (s *session) Proto() *spannerpb.Session {\n\tctime := timestamppb.New(s.createdAt)\n\tlast := timestamppb.New(s.lastUse)\n\treturn &spannerpb.Session{\n\t\tName:                   s.name,\n\t\tCreateTime:             ctime,\n\t\tApproximateLastUseTime: last,\n\t}\n}\n\nfunc (s *session) createTransaction(txMode transactionMode, single bool) (*transaction, error) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\tfor i := 0; i < 3; i++ {\n\t\ttx := newTransaction(s, txMode, single)\n\t\tif _, ok := s.transactions[tx.Name()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\ts.transactions[tx.Name()] = tx\n\n\t\tif err := s.database.BeginTransaction(tx); err != nil {\n\t\t\treturn nil, err // TODO\n\t\t}\n\n\t\t// read write transactions are kept only 32 in a session\n\t\t// it seems no limitation for read only transactions\n\t\tif txMode == txReadWrite {\n\t\t\ts.activeRWTx = append(s.activeRWTx, tx)\n\t\t\tif len(s.activeRWTx) > 32 {\n\t\t\t\toldtx := s.activeRWTx[0]\n\t\t\t\ts.activeRWTx = s.activeRWTx[1:]\n\t\t\t\toldtx.Done(TransactionInvalidated)\n\t\t\t}\n\t\t}\n\t\treturn tx, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"failed to create transaction\")\n}\n\nfunc (s *session) GetTransaction(id []byte) (*transaction, bool) {\n\ts.mu.Lock()\n\ttx, ok := s.transactions[string(id)]\n\ts.mu.Unlock()\n\treturn tx, ok\n}\n\n// GetTransactionBySelector returns a transaction by selector from session.\n// The second return value means a new transaction is created or not.\n// When the selector specifies Begin, true is returned. Otherwise false even if SingleUse option is specified.\nfunc (s *session) GetTransactionBySelector(txsel *spannerpb.TransactionSelector) (*transaction, bool, error) {\n\tswitch sel := txsel.GetSelector().(type) {\n\tcase nil:\n\t\t// From documents: If none is provided, the default is a\n\t\t// single use transaction with strong concurrency.\n\t\ttx, err := s.createTransaction(txReadWrite, true)\n\t\treturn tx, false, err\n\tcase *spannerpb.TransactionSelector_SingleUse:\n\t\ttx, err := s.createTransaction(txReadWrite, true)\n\t\treturn tx, false, err\n\tcase *spannerpb.TransactionSelector_Id:\n\t\ttx, ok := s.GetTransaction(sel.Id)\n\t\tif !ok {\n\t\t\treturn nil, false, status.Errorf(codes.InvalidArgument, \"Transaction was started in a different session\")\n\t\t}\n\t\treturn tx, false, nil\n\tcase *spannerpb.TransactionSelector_Begin:\n\t\ttx, err := s.BeginTransaction(sel.Begin)\n\t\treturn tx, true, err\n\tdefault:\n\t\treturn nil, false, fmt.Errorf(\"unknown transaction selector: %v\", sel)\n\t}\n}\n\n// GetTransactionForCommit returns a transaction by selector from session.\n// The argument is expected to be spannerpb.isCommitRequest_Transaction. It is not exported so interface{}\n// is used instead.\nfunc (s *session) GetTransactionForCommit(txsel interface{}) (*transaction, error) {\n\tswitch v := txsel.(type) {\n\tcase *spannerpb.CommitRequest_TransactionId:\n\t\ttx, ok := s.GetTransaction(v.TransactionId)\n\t\tif !ok {\n\t\t\treturn nil, status.Errorf(codes.InvalidArgument, \"Transaction was started in a different session\")\n\t\t}\n\t\treturn tx, nil\n\tcase *spannerpb.CommitRequest_SingleUseTransaction:\n\t\treturn s.beginTransaction(v.SingleUseTransaction, true)\n\tdefault:\n\t\treturn nil, status.Errorf(codes.Unknown, \"unknown transaction: %v\", v)\n\t}\n}\n\n// BeginTransaction creates a new transaction for a session.\nfunc (s *session) BeginTransaction(opt *spannerpb.TransactionOptions) (*transaction, error) {\n\treturn s.beginTransaction(opt, false)\n}\n\nfunc (s *session) beginTransaction(opt *spannerpb.TransactionOptions, single bool) (*transaction, error) {\n\tvar txMode transactionMode\n\tswitch v := opt.GetMode().(type) {\n\tcase *spannerpb.TransactionOptions_ReadWrite_:\n\t\ttxMode = txReadWrite\n\tcase *spannerpb.TransactionOptions_ReadOnly_:\n\t\ttxMode = txReadOnly\n\tcase *spannerpb.TransactionOptions_PartitionedDml_:\n\t\ttxMode = txPartitionedDML\n\tcase nil:\n\t\t// TransactionOptions is required\n\t\treturn nil, status.Errorf(codes.InvalidArgument, \"Invalid BeginTransaction request\")\n\tdefault:\n\t\treturn nil, status.Errorf(codes.Unknown, \"unknown transaction mode: %v\", v)\n\t}\n\n\ttx, err := s.createTransaction(txMode, single)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tx, nil\n}\n\nfunc newSession(db *database, dbName string) *session {\n\tid := uuidpkg.New().String()\n\treturn &session{\n\t\tid:           id,\n\t\tdatabase:     db,\n\t\tdbName:       dbName,\n\t\tname:         fmt.Sprintf(\"%s/sessions/%s\", dbName, id),\n\t\tcreatedAt:    time.Now(),\n\t\ttransactions: make(map[string]*transaction),\n\t\tactiveRWTx:   make([]*transaction, 0, 8),\n\t}\n}\n"
  },
  {
    "path": "server/spanner_error.go",
    "content": "// Copyright 2020 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"fmt\"\n\n\t\"google.golang.org/genproto/googleapis/rpc/errdetails\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nfunc newSpannerDatabaseNotFoundError(name string) error {\n\tdetail := &errdetails.ResourceInfo{\n\t\tResourceType: \"type.googleapis.com/google.spanner.admin.database.v1.Database\",\n\t\tResourceName: name,\n\t\tDescription:  \"Database does not exist.\",\n\t}\n\tst, err := status.Newf(codes.NotFound, \"Database not found: %s\", name).WithDetails(detail)\n\tif err != nil {\n\t\treturn status.Errorf(codes.Internal, \"failed to build status error\")\n\t}\n\n\treturn st.Err()\n}\n\nfunc newSpannerSessionNotFoundError(name string) error {\n\tdetail := &errdetails.ResourceInfo{\n\t\tResourceType: \"type.googleapis.com/google.spanner.v1.Session\",\n\t\tResourceName: name,\n\t\tDescription:  \"Session does not exist.\",\n\t}\n\tst, err := status.Newf(codes.NotFound, \"Session not found: %s\", name).WithDetails(detail)\n\tif err != nil {\n\t\treturn status.Errorf(codes.Internal, \"failed to build status error\")\n\t}\n\n\treturn st.Err()\n}\n\nfunc newSpannerTableNotFoundError(name string) error {\n\tdetail := &errdetails.ResourceInfo{\n\t\tResourceType: \"spanner.googleapis.com/Table\",\n\t\tResourceName: name,\n\t\tDescription:  \"Table not found\",\n\t}\n\tst, err := status.Newf(codes.NotFound, \"Table not found: %s\", name).WithDetails(detail)\n\tif err != nil {\n\t\treturn status.Errorf(codes.Internal, \"failed to build status error\")\n\t}\n\n\treturn st.Err()\n}\n\nfunc newSpannerColumnNotFoundError(table, name string) error {\n\tdetail := &errdetails.ResourceInfo{\n\t\tResourceType: \"spanner.googleapis.com/Column\",\n\t\tResourceName: name,\n\t\t// no description in real spanner\n\t}\n\tst, err := status.Newf(codes.NotFound, \"Column not found in table %s: %s\", table, name).WithDetails(detail)\n\tif err != nil {\n\t\treturn status.Errorf(codes.Internal, \"failed to build status error\")\n\t}\n\n\treturn st.Err()\n}\n\nfunc newSpannerIndexnNotFoundError(table, name string) error {\n\tdetail := &errdetails.ResourceInfo{\n\t\tResourceType: \"spanner.googleapis.com/Index\",\n\t\tResourceName: name,\n\t\tDescription:  fmt.Sprintf(\"Index not found on table %s\", table),\n\t}\n\tst, err := status.Newf(codes.NotFound, \"Index not found on table %s: %s\", table, name).WithDetails(detail)\n\tif err != nil {\n\t\treturn status.Errorf(codes.Internal, \"failed to build status error\")\n\t}\n\n\treturn st.Err()\n}\n"
  },
  {
    "path": "server/table.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/cloudspannerecosystem/memefish/ast\"\n)\n\ntype Table struct {\n\tast  *ast.CreateTable\n\tName string\n\n\tcolumns    []*Column\n\tcolumnsMap map[string]*Column\n\n\tprimaryKey *TableIndex\n\tindex      []*TableIndex\n}\n\nfunc newTable() *Table {\n\treturn &Table{\n\t\tcolumnsMap: make(map[string]*Column),\n\t}\n}\n\nfunc (t *Table) TableIndex(idx string) (*TableIndex, bool) {\n\tif idx == \"\" {\n\t\treturn t.primaryKey, true\n\t}\n\n\tfor _, index := range t.index {\n\t\tif index.Name() == idx {\n\t\t\treturn index, true\n\t\t}\n\t}\n\n\treturn nil, false\n}\n\nfunc (t *Table) TableView() *TableView {\n\treturn createTableViewFromTable(t, \"\")\n}\n\nfunc (t *Table) TableViewWithAlias(alias string) *TableView {\n\treturn createTableViewFromTable(t, alias)\n}\n\n// NonNullableAndNonGeneratedColumnsExist checks non nullable columns exist in the spciefied columns.\n// It returns true and the columns if non nullable and non generated columns exist.\nfunc (t *Table) NonNullableAndNonGeneratedColumnsExist(columns []string) (bool, []string) {\n\tusedColumns := make(map[string]struct{}, len(columns))\n\tfor _, name := range columns {\n\t\tusedColumns[name] = struct{}{}\n\t}\n\n\tvar noExistNonNullableColumns []string\n\tfor _, c := range t.columns {\n\t\tif c.nullable {\n\t\t\tcontinue\n\t\t}\n\t\tif c.ast != nil && c.ast.DefaultSemantics != nil {\n\t\t\tif _, ok := c.ast.DefaultSemantics.(*ast.GeneratedColumnExpr); ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tn := c.Name()\n\t\tif _, ok := usedColumns[n]; !ok {\n\t\t\tnoExistNonNullableColumns = append(noExistNonNullableColumns, n)\n\t\t}\n\t}\n\n\tif len(noExistNonNullableColumns) > 0 {\n\t\treturn true, noExistNonNullableColumns\n\t}\n\n\treturn false, nil\n}\n\nfunc createTableFromAST(stmt *ast.CreateTable) (*Table, error) {\n\tt := newTable()\n\tt.Name = stmt.Name.Idents[0].Name\n\tt.ast = stmt\n\n\tfor _, col := range stmt.Columns {\n\t\tt.addColumn(col)\n\t}\n\tt.reorderColumnPosition()\n\n\tif err := t.setPrimaryKeys(stmt.PrimaryKeys); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn t, nil\n}\n\nfunc (t *Table) addColumn(col *ast.ColumnDef) {\n\tcolumn := newColumn(col)\n\tt.columns = append(t.columns, column)\n\tt.columnsMap[column.Name()] = column\n}\n\nfunc (t *Table) reorderColumnPosition() {\n\tfor i := range t.columns {\n\t\tt.columns[i].setPosition(i + 1)\n\t}\n}\n\nfunc (t *Table) setPrimaryKeys(pkey []*ast.IndexKey) error {\n\tindex, err := createPrimaryKey(t, pkey)\n\tif err != nil {\n\t\treturn err\n\t}\n\tt.primaryKey = index\n\n\tfor i, col := range index.IndexColumns() {\n\t\tcol.markPrimaryKey(i + 1)\n\t}\n\n\treturn nil\n}\n\nfunc (t *Table) createIndex(stmt *ast.CreateIndex) (*TableIndex, error) {\n\tidx, err := createTableIndexFromAST(t, stmt)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tt.index = append(t.index, idx)\n\treturn idx, nil\n}\n\nfunc (t *Table) getColumn(name string) (*Column, error) {\n\tc, ok := t.columnsMap[name]\n\tif !ok {\n\t\treturn nil, newSpannerColumnNotFoundError(t.Name, name)\n\t}\n\treturn c, nil\n}\n\nfunc (t *Table) getColumnsByName(names []string) ([]*Column, error) {\n\tcolumns := make([]*Column, len(names))\n\tfor i, name := range names {\n\t\tc, err := t.getColumn(name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcolumns[i] = c\n\t}\n\n\treturn columns, nil\n}\n\ntype Column struct {\n\tast *ast.ColumnDef\n\tpos int\n\n\talias string\n\n\tvalueType  ValueType\n\tdbDataType dbDataType\n\n\tnullable bool\n\tisArray  bool\n\tisSized  bool\n\tisMax    bool\n\tsize     int64\n\n\tallowCommitTimestamp bool\n\n\tisPrimaryKey  bool\n\tprimaryKeyPos int\n}\n\nfunc (c *Column) Name() string {\n\treturn c.ast.Name.Name\n}\n\nfunc (c *Column) Alias() string {\n\tif c.alias != \"\" {\n\t\treturn c.alias\n\t}\n\treturn c.ast.Name.Name\n}\n\nfunc (c *Column) setPosition(pos int) {\n\tc.pos = pos\n}\n\nfunc (c *Column) markPrimaryKey(pos int) {\n\tc.isPrimaryKey = true\n\tc.primaryKeyPos = pos\n}\n\ntype columnType struct {\n\tdataType ast.ScalarTypeName\n\n\tisArray bool\n\tisSized bool\n\tisMax   bool\n\tsize    int64\n}\n\ntype dbDataType string\n\nconst (\n\tDBDTInteger dbDataType = \"INTEGER\"\n\tDBDTReal    dbDataType = \"REAL\"\n\tDBDTText    dbDataType = \"TEXT\"\n\tDBDTBlob    dbDataType = \"BLOB\"\n\tDBDTJson    dbDataType = \"JSON\"\n)\n\nfunc (ct columnType) SqliteDataType() string {\n\tswitch ct.dataType {\n\tcase ast.BoolTypeName:\n\t\treturn \"INTEGER\"\n\tcase ast.Int64TypeName:\n\t\treturn \"INTEGER\"\n\tcase ast.Float64TypeName:\n\t\treturn \"REAL\"\n\tcase ast.StringTypeName:\n\t\treturn \"TEXT\"\n\tcase ast.BytesTypeName:\n\t\treturn \"BLOB\"\n\tcase ast.DateTypeName:\n\t\treturn \"TEXT\"\n\tcase ast.TimestampTypeName:\n\t\treturn \"TEXT\"\n\t}\n\n\tpanic(fmt.Sprintf(\"unknown data type: %s\", ct.dataType))\n}\n\nfunc newColumn(def *ast.ColumnDef) *Column {\n\tct := toColumnType(def.Type)\n\tvt := toValueType(def.Type)\n\n\tvar dbdt dbDataType\n\tswitch vt.Code {\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"unknown value type %#v\", vt))\n\tcase TCBool:\n\t\tdbdt = DBDTInteger\n\tcase TCInt64:\n\t\tdbdt = DBDTInteger\n\tcase TCFloat64:\n\t\tdbdt = DBDTReal\n\tcase TCString:\n\t\tdbdt = DBDTText\n\tcase TCBytes:\n\t\tdbdt = DBDTBlob\n\tcase TCDate:\n\t\tdbdt = DBDTText\n\tcase TCTimestamp:\n\t\tdbdt = DBDTText\n\tcase TCArray:\n\t\tdbdt = DBDTJson\n\tcase TCJson:\n\t\tdbdt = DBDTJson\n\t}\n\n\tvar allowCommitTimestamp bool\n\tif def.Options != nil {\n\t\tif val, err := def.Options.BoolField(\"allow_commit_timestamp\"); err == nil && val != nil {\n\t\t\tallowCommitTimestamp = *val\n\t\t}\n\t}\n\n\treturn &Column{\n\t\tast: def,\n\n\t\tvalueType:  vt,\n\t\tdbDataType: dbdt,\n\n\t\tnullable: !def.NotNull,\n\t\tisArray:  ct.isArray,\n\t\tisSized:  ct.isSized,\n\t\tisMax:    ct.isMax,\n\t\tsize:     ct.size,\n\n\t\tallowCommitTimestamp: allowCommitTimestamp,\n\t}\n}\n\nfunc astTypeToTypeCode(astTypeName ast.ScalarTypeName) TypeCode {\n\tswitch astTypeName {\n\tcase ast.BoolTypeName:\n\t\treturn TCBool\n\tcase ast.Int64TypeName:\n\t\treturn TCInt64\n\tcase ast.Float64TypeName:\n\t\treturn TCFloat64\n\tcase ast.StringTypeName:\n\t\treturn TCString\n\tcase ast.BytesTypeName:\n\t\treturn TCBytes\n\tcase ast.DateTypeName:\n\t\treturn TCDate\n\tcase ast.TimestampTypeName:\n\t\treturn TCTimestamp\n\tcase ast.ScalarTypeName(\"JSON\"):\n\t\treturn TCJson\n\tdefault:\n\t\tpanic(\"unknown type\")\n\t}\n}\n\nfunc toValueType(t ast.SchemaType) ValueType {\n\tswitch v := t.(type) {\n\tcase *ast.ScalarSchemaType:\n\t\treturn ValueType{Code: astTypeToTypeCode(v.Name)}\n\n\tcase *ast.SizedSchemaType:\n\t\treturn ValueType{Code: astTypeToTypeCode(v.Name)}\n\n\tcase *ast.ArraySchemaType:\n\t\tarrType := toValueType(v.Item)\n\t\treturn ValueType{\n\t\t\tCode:      TCArray,\n\t\t\tArrayType: &arrType,\n\t\t}\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"unknow type %v\", t))\n\t}\n}\n\nfunc schemaTypetoTypString(t ast.SchemaType) string {\n\tswitch v := t.(type) {\n\tcase *ast.ScalarSchemaType:\n\t\treturn astTypeToTypeCode(v.Name).String()\n\n\tcase *ast.SizedSchemaType:\n\t\ttyp := astTypeToTypeCode(v.Name).String()\n\t\tsize := \"MAX\"\n\t\tif !v.Max {\n\t\t\tintLit := v.Size.(*ast.IntLiteral)\n\t\t\tsize = intLit.Value // TODO: respect base?\n\t\t}\n\t\treturn fmt.Sprintf(\"%s(%s)\", typ, size)\n\n\tcase *ast.ArraySchemaType:\n\t\tarrType := schemaTypetoTypString(v.Item)\n\t\treturn fmt.Sprintf(\"ARRAY<%s>\", arrType)\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"unknow type %v\", t))\n\t}\n}\n\nfunc toColumnType(t ast.SchemaType) columnType {\n\tswitch v := t.(type) {\n\tcase *ast.ScalarSchemaType:\n\t\treturn columnType{\n\t\t\tdataType: v.Name,\n\t\t}\n\tcase *ast.SizedSchemaType:\n\t\tif v.Max {\n\t\t\treturn columnType{\n\t\t\t\tdataType: v.Name,\n\t\t\t\tisSized:  true,\n\t\t\t\tisMax:    true,\n\t\t\t}\n\t\t}\n\n\t\tintLit, ok := v.Size.(*ast.IntLiteral)\n\t\tif !ok {\n\t\t\tpanic(fmt.Sprintf(\"expected IntLiteral but %v\", v.Size))\n\t\t}\n\n\t\tn, err := strconv.ParseInt(intLit.Value, intLit.Base, 64)\n\t\tif err != nil {\n\t\t\tpanic(fmt.Sprintf(\"cannot parse IntLiteral: %v\", intLit))\n\t\t}\n\n\t\treturn columnType{\n\t\t\tdataType: v.Name,\n\t\t\tisSized:  true,\n\t\t\tisMax:    false,\n\t\t\tsize:     n,\n\t\t}\n\n\tcase *ast.ArraySchemaType:\n\t\tct := toColumnType(v.Item)\n\t\tct.isArray = true\n\t\treturn ct\n\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"unknow type %v\", t))\n\t}\n\n}\n\ntype TableIndex struct {\n\tast          *ast.CreateIndex\n\tastIndexKeys []*ast.IndexKey\n\n\tname  string\n\ttable *Table\n\n\tunique       bool\n\tnullFiltered bool\n\n\tcolumnsRef      []*Column\n\tcolumnNames     []string\n\tcolumnDirctions []string\n\tstoredColumns   map[string]struct{}\n}\n\nfunc createTableIndexFromAST(table *Table, stmt *ast.CreateIndex) (*TableIndex, error) {\n\treturn createTableIndex(table, stmt.Keys, stmt)\n}\n\nfunc createPrimaryKey(table *Table, pkeys []*ast.IndexKey) (*TableIndex, error) {\n\treturn createTableIndex(table, pkeys, nil)\n}\n\nfunc createTableIndex(table *Table, keys []*ast.IndexKey, secondaryIdx *ast.CreateIndex) (*TableIndex, error) {\n\tcolumns := make([]*Column, len(keys))\n\tcolumnNames := make([]string, len(keys))\n\tcolumnDirctions := make([]string, len(keys))\n\tfor i, key := range keys {\n\t\tcol, ok := table.columnsMap[key.Name.Name]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"primary key not found: %s\", key.Name.Name)\n\t\t}\n\n\t\tcolumns[i] = col\n\t\tcolumnNames[i] = col.Name()\n\t\tdir := string(key.Dir)\n\t\tif dir == \"\" { // work around\n\t\t\tdir = \"ASC\"\n\t\t}\n\t\tcolumnDirctions[i] = dir\n\t}\n\n\tname := \"PRIMARY_KEY\"\n\tunique := true\n\tnullFiltered := false\n\tstoredColumns := make(map[string]struct{})\n\n\tif secondaryIdx != nil {\n\t\tname = secondaryIdx.Name.Idents[0].Name\n\t\tunique = secondaryIdx.Unique\n\t\tnullFiltered = secondaryIdx.NullFiltered\n\n\t\t// columns for Index Keys\n\t\tfor _, c := range columns {\n\t\t\tstoredColumns[c.Name()] = struct{}{}\n\t\t}\n\n\t\t// secondary index also has primary key columns by default\n\t\tfor _, name := range table.primaryKey.IndexColumnNames() {\n\t\t\tstoredColumns[name] = struct{}{}\n\t\t}\n\n\t\t// storing columns\n\t\tif secondaryIdx.Storing != nil {\n\t\t\tfor _, c := range secondaryIdx.Storing.Columns {\n\t\t\t\tstoredColumns[c.Name] = struct{}{}\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Primry Keys have all columns\n\t\tfor _, c := range table.columns {\n\t\t\tstoredColumns[c.Name()] = struct{}{}\n\t\t}\n\t}\n\n\treturn &TableIndex{\n\t\tast:          secondaryIdx,\n\t\tastIndexKeys: keys,\n\n\t\tname:  name,\n\t\ttable: table,\n\n\t\tunique:       unique,\n\t\tnullFiltered: nullFiltered,\n\n\t\tcolumnsRef:      columns,\n\t\tcolumnNames:     columnNames,\n\t\tstoredColumns:   storedColumns,\n\t\tcolumnDirctions: columnDirctions,\n\t}, nil\n}\n\nfunc (i *TableIndex) Name() string {\n\treturn i.name\n}\n\nfunc (i *TableIndex) IndexColumns() []*Column {\n\treturn i.columnsRef\n}\n\nfunc (i *TableIndex) IndexColumnNames() []string {\n\treturn i.columnNames\n}\n\nfunc (i *TableIndex) IndexColumnDirections() []string {\n\treturn i.columnDirctions\n}\n\nfunc (i *TableIndex) HasColumn(c string) bool {\n\t_, ok := i.storedColumns[c]\n\treturn ok\n}\n\ntype TableView struct {\n\tResultItems    []ResultItem\n\tResultItemsMap map[string]ResultItem\n\tambiguous      map[string]struct{}\n}\n\nfunc (v *TableView) AllItems() []ResultItem {\n\treturn v.ResultItems\n}\n\nfunc (v *TableView) Get(id string) (ResultItem, bool, bool) {\n\tif _, ok := v.ambiguous[id]; ok {\n\t\treturn ResultItem{}, true, false\n\t}\n\titem, ok := v.ResultItemsMap[id]\n\tif !ok {\n\t\treturn ResultItem{}, false, true\n\t}\n\treturn item, false, false\n}\n\nfunc (v *TableView) ToStruct() *StructType {\n\tnames := make([]string, len(v.ResultItems))\n\tvts := make([]*ValueType, len(v.ResultItems))\n\tfor i := range v.ResultItems {\n\t\tnames[i] = v.ResultItems[i].Name\n\t\tvts[i] = &v.ResultItems[i].ValueType\n\t}\n\n\treturn &StructType{\n\t\tFieldNames: names,\n\t\tFieldTypes: vts,\n\t\tIsTable:    true,\n\t}\n}\n\nfunc createTableViewFromItems(items1 []ResultItem, items2 []ResultItem) *TableView {\n\tnewItems := make([]ResultItem, 0, len(items1)+len(items2))\n\tnewItemsMap := make(map[string]ResultItem, len(items1)+len(items2))\n\tambiguous := make(map[string]struct{})\n\n\tfor _, items := range [][]ResultItem{items1, items2} {\n\t\tfor _, item := range items {\n\t\t\tnewItems = append(newItems, item)\n\t\t\tif item.Name != \"\" {\n\t\t\t\t_, ok := newItemsMap[item.Name]\n\t\t\t\tif ok {\n\t\t\t\t\tambiguous[item.Name] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tnewItemsMap[item.Name] = item\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &TableView{\n\t\tResultItems:    newItems,\n\t\tResultItemsMap: newItemsMap,\n\t\tambiguous:      ambiguous,\n\t}\n}\n\nfunc createTableViewFromTable(table *Table, alias string) *TableView {\n\titems := make([]ResultItem, 0, len(table.columns))\n\titemsMap := make(map[string]ResultItem, len(table.columns))\n\tfor _, column := range table.columns {\n\t\titem := createResultItemFromColumn(column)\n\t\t// if alias specified, add the alias to Expr\n\t\tif alias != \"\" {\n\t\t\titem.Expr.Raw = fmt.Sprintf(\"%s.%s\", QuoteString(alias), item.Expr.Raw)\n\t\t}\n\t\titems = append(items, item)\n\t\titemsMap[column.Name()] = item\n\t}\n\treturn &TableView{\n\t\tResultItems:    items,\n\t\tResultItemsMap: itemsMap,\n\t}\n}\n\ntype ResultItem struct {\n\tName      string\n\tValueType ValueType\n\n\tExpr Expr\n}\n\nfunc createResultItemFromColumn(column *Column) ResultItem {\n\treturn ResultItem{\n\t\tName:      column.Name(),\n\t\tValueType: column.valueType,\n\t\tExpr: Expr{\n\t\t\tRaw:       QuoteString(column.Name()),\n\t\t\tValueType: column.valueType,\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "server/transaction.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\tuuidpkg \"github.com/google/uuid\"\n\tspannerpb \"google.golang.org/genproto/googleapis/spanner/v1\"\n)\n\ntype databaseReader interface {\n\tQueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)\n}\n\ntype databaseWriter interface {\n\tExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)\n\tCommit() error\n\tRollback() error\n}\n\nvar (\n\tErrNotStartedTransaction   = fmt.Errorf(\"transaction is not started\")\n\tErrNotAvailableTransaction = fmt.Errorf(\"transaction is not available\")\n\tErrInvalidatedTransaction  = fmt.Errorf(\"transaction is invalidated\")\n)\n\ntype transactionMode int\n\nconst (\n\ttxReadOnly       transactionMode = 1\n\ttxReadWrite      transactionMode = 2\n\ttxPartitionedDML transactionMode = 3\n)\n\ntype TransactionStatus int\n\nconst (\n\tTransactionActive      TransactionStatus = 1\n\tTransactionInvalidated TransactionStatus = 2\n\tTransactionCommited    TransactionStatus = 3\n\tTransactionRollbacked  TransactionStatus = 4\n\tTransactionAborted     TransactionStatus = 5\n)\n\nfunc (s TransactionStatus) String() string {\n\tswitch s {\n\tcase TransactionActive:\n\t\treturn \"ACTIVE\"\n\tcase TransactionInvalidated:\n\t\treturn \"INVALIDATED\"\n\tcase TransactionCommited:\n\t\treturn \"COMMITED\"\n\tcase TransactionRollbacked:\n\t\treturn \"ROLLBACKED\"\n\tcase TransactionAborted:\n\t\treturn \"ABORTED\"\n\tdefault:\n\t\treturn \"UNKNOWN\"\n\t}\n}\n\ntype transaction struct {\n\tid        []byte\n\tname      string\n\tsession   *session\n\tsingle    bool\n\tmode      transactionMode\n\tstatus    int32 // for atomic operation\n\tcreatedAt time.Time\n\n\tctx    context.Context\n\tcancel func()\n\n\tmu    sync.RWMutex\n\ttx    *sql.Tx\n\tclose func(*transaction, *sql.Tx) // database.endTransaction()\n}\n\nfunc (tx *transaction) Context() context.Context {\n\treturn tx.ctx\n}\n\nfunc (tx *transaction) Equals(t *transaction) bool {\n\tif tx == nil || t == nil {\n\t\treturn false\n\t}\n\n\tif bytes.Equal(t.ID(), tx.ID()) {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc (tx *transaction) ID() []byte {\n\treturn tx.id\n}\n\nfunc (tx *transaction) Name() string {\n\tif tx == nil {\n\t\treturn \"<nil>\"\n\t}\n\treturn tx.name\n}\n\nfunc (tx *transaction) Proto() *spannerpb.Transaction {\n\treturn &spannerpb.Transaction{\n\t\tId: tx.id,\n\t}\n}\n\nfunc (tx *transaction) Status() TransactionStatus {\n\treturn TransactionStatus(atomic.LoadInt32(&tx.status))\n}\n\nfunc (tx *transaction) ReadWrite() bool {\n\treturn tx.mode == txReadWrite\n}\n\nfunc (tx *transaction) Available() bool {\n\treturn tx.Status() == TransactionActive\n}\n\nfunc (tx *transaction) Invalidated() bool {\n\tst := tx.Status()\n\treturn st == TransactionInvalidated || st == TransactionAborted\n}\n\nfunc (tx *transaction) SingleUse() bool {\n\treturn tx.single\n}\n\nfunc (tx *transaction) Done(status TransactionStatus) {\n\ttx.mu.Lock()\n\tdefer tx.mu.Unlock()\n\n\t// skip if the transaction is done already\n\tif tx.Status() != TransactionActive {\n\t\treturn\n\t}\n\n\tDebugf(\"[%s] transaction.Done %s\\n\", tx.Name(), status)\n\tatomic.StoreInt32(&tx.status, int32(status))\n\n\tif tx.close != nil {\n\t\ttx.close(tx, tx.tx)\n\t\ttx.close = nil\n\t}\n\n\t// Cancling transaction context is very unstable.\n\t// Call cancel after explicitly stop the transaction.\n\ttx.cancel()\n}\n\nfunc (tx *transaction) SetTransaction(dbtx *sql.Tx, closer func(*transaction, *sql.Tx)) error {\n\ttx.mu.Lock()\n\tdefer tx.mu.Unlock()\n\n\tif tx.tx != nil {\n\t\treturn fmt.Errorf(\"transaction already started\")\n\t}\n\n\ttx.tx = dbtx\n\ttx.close = closer\n\n\treturn nil\n}\n\nfunc (tx *transaction) WriteTransaction(fn func(databaseWriter) error) error {\n\tif IsDebug() {\n\t\tdefer DebugStartEnd(\"[%s] transaction.WriteTransaction\", tx.Name())()\n\t}\n\n\ttx.mu.Lock()\n\tdefer tx.mu.Unlock()\n\n\tif !tx.Available() {\n\t\treturn ErrNotAvailableTransaction\n\t}\n\n\tif tx.tx == nil {\n\t\treturn ErrNotStartedTransaction\n\t}\n\n\treturn fn(tx.tx)\n}\n\nfunc (tx *transaction) ReadTransaction(fn func(context.Context, databaseReader) error) error {\n\tif IsDebug() {\n\t\tdefer DebugStartEnd(\"[%s] transaction.ReadTransaction\", tx.Name())()\n\t}\n\n\ttx.mu.Lock()\n\tdefer tx.mu.Unlock()\n\n\tif !tx.Available() {\n\t\treturn ErrNotAvailableTransaction\n\t}\n\n\tif tx.tx == nil {\n\t\treturn ErrNotStartedTransaction\n\t}\n\n\treturn fn(tx.ctx, tx.tx)\n}\n\nfunc newTransaction(s *session, mode transactionMode, single bool) *transaction {\n\tid := uuidpkg.New().String()\n\tctx, cancel := context.WithCancel(context.Background())\n\treturn &transaction{\n\t\tid:        []byte(id),\n\t\tname:      id,\n\t\tsession:   s,\n\t\tsingle:    single,\n\t\tmode:      mode,\n\t\tstatus:    int32(TransactionActive),\n\t\tcreatedAt: time.Now(),\n\n\t\tctx:    ctx,\n\t\tcancel: cancel,\n\t}\n}\n"
  },
  {
    "path": "server/value.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"database/sql\"\n\t\"database/sql/driver\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tspannerpb \"google.golang.org/genproto/googleapis/spanner/v1\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tstructpb \"google.golang.org/protobuf/types/known/structpb\"\n)\n\nvar NullExpr = Expr{}\nvar NullValue = Value{}\n\ntype Expr struct {\n\tRaw       string\n\tValueType ValueType\n\tArgs      []interface{}\n}\n\ntype RowIterator interface {\n\tResultSet() []ResultItem\n\n\tDo(func([]interface{}) error) error\n}\n\ntype Value struct {\n\tData interface{}\n\tType ValueType\n}\n\ntype ValueType struct {\n\tCode       TypeCode\n\tArrayType  *ValueType\n\tStructType *StructType\n}\n\nfunc (t ValueType) IsArray() bool {\n\treturn t.Code == TCArray\n}\n\nfunc (t ValueType) IsStruct() bool {\n\treturn t.Code == TCStruct\n}\n\nfunc (t ValueType) String() string {\n\tswitch t.Code {\n\tcase TCBool, TCInt64, TCFloat64, TCTimestamp, TCDate, TCString, TCBytes:\n\t\treturn t.Code.String()\n\tcase TCArray:\n\t\treturn fmt.Sprintf(\"ARRAY<%s>\", t.ArrayType.String())\n\tcase TCStruct:\n\t\tn := len(t.StructType.FieldTypes)\n\t\tss := make([]string, n)\n\t\tfor i := 0; i < n; i++ {\n\t\t\tname := t.StructType.FieldNames[i]\n\t\t\tvt := t.StructType.FieldTypes[i]\n\t\t\tif name == \"\" {\n\t\t\t\tss[i] = vt.String()\n\t\t\t} else {\n\t\t\t\tss[i] = name + \" \" + vt.String()\n\n\t\t\t}\n\t\t}\n\t\treturn fmt.Sprintf(\"STRUCT<%s>\", strings.Join(ss, \", \"))\n\t}\n\treturn \"(unknown type)\"\n}\n\nfunc compareValueType(a, b ValueType) bool {\n\tif a.Code != b.Code {\n\t\treturn false\n\t}\n\n\tif a.Code == TCStruct && b.Code == TCStruct {\n\t\taStr := a.StructType\n\t\tbStr := b.StructType\n\n\t\tif len(aStr.FieldTypes) != len(bStr.FieldTypes) {\n\t\t\treturn false\n\t\t}\n\t\tfor i := 0; i < len(aStr.FieldTypes); i++ {\n\t\t\tb := compareValueType(*aStr.FieldTypes[i], *bStr.FieldTypes[i])\n\t\t\tif !b {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\n\t\treturn true\n\t}\n\n\tif a.Code == TCArray && b.Code == TCArray {\n\t\treturn compareValueType(*a.ArrayType, *b.ArrayType)\n\t}\n\n\treturn true\n}\n\nfunc compatibleValueType(a, b ValueType) (ValueType, bool) {\n\tif a.Code == TCInt64 && b.Code == TCFloat64 {\n\t\treturn b, true\n\t}\n\tif b.Code == TCInt64 && a.Code == TCFloat64 {\n\t\treturn a, true\n\t}\n\treturn a, a == b\n}\n\nfunc decideArrayElementsValueType(vts ...ValueType) (ValueType, error) {\n\tvt := ValueType{Code: TCInt64}\n\tif len(vts) > 0 {\n\t\tvt = vts[0]\n\t}\n\n\tused := map[string]struct{}{}\n\n\tfor i := range vts {\n\t\tused[vts[i].String()] = struct{}{}\n\t}\n\n\tfor i := range vts {\n\t\tvar ok bool\n\t\t// TODO: if ValueType is changed, types of all values also need changing\n\t\t// vt, ok = compatibleValueType(vt, vts[i])\n\t\tok = compareValueType(vt, vts[i])\n\t\tif !ok {\n\t\t\tvar typ string\n\t\t\tfirst := true\n\t\t\tfor n := range used {\n\t\t\t\tif !first {\n\t\t\t\t\ttyp += \", \"\n\t\t\t\t}\n\t\t\t\ttyp += n\n\t\t\t\tfirst = false\n\t\t\t}\n\n\t\t\treturn ValueType{}, fmt.Errorf(\"Array elements of types {%s} do not have a common supertype\", typ)\n\t\t}\n\t}\n\n\treturn vt, nil\n}\n\ntype StructType struct {\n\tFieldNames []string\n\tFieldTypes []*ValueType\n\n\t// Table can be struct but it behaves differently.\n\t// So a struct created from table should be marked.\n\tIsTable bool\n}\n\nfunc (s *StructType) AllItems() []ResultItem {\n\tn := len(s.FieldTypes)\n\titems := make([]ResultItem, n)\n\tfor i := 0; i < n; i++ {\n\t\tname := s.FieldNames[i]\n\t\tvt := s.FieldTypes[i]\n\t\titems[i] = ResultItem{\n\t\t\tName:      name,\n\t\t\tValueType: *vt,\n\t\t\tExpr: Expr{\n\t\t\t\tRaw:       name,\n\t\t\t\tValueType: *vt,\n\t\t\t},\n\t\t}\n\t}\n\n\treturn items\n}\n\ntype TypeCode int32\n\nfunc (c TypeCode) String() string {\n\tswitch c {\n\tcase TCBool:\n\t\treturn \"BOOL\"\n\tcase TCInt64:\n\t\treturn \"INT64\"\n\tcase TCFloat64:\n\t\treturn \"FLOAT64\"\n\tcase TCTimestamp:\n\t\treturn \"TIMESTAMP\"\n\tcase TCDate:\n\t\treturn \"DATE\"\n\tcase TCString:\n\t\treturn \"STRING\"\n\tcase TCBytes:\n\t\treturn \"BYTES\"\n\tcase TCArray:\n\t\treturn \"ARRAY\"\n\tcase TCStruct:\n\t\treturn \"STRUCT\"\n\tdefault:\n\t\treturn \"(unknown)\"\n\t}\n}\n\nconst (\n\tTCBool TypeCode = iota + 1\n\tTCInt64\n\tTCFloat64\n\tTCTimestamp\n\tTCDate\n\tTCString\n\tTCBytes\n\tTCArray\n\tTCStruct\n\tTCJson\n)\n\ntype ArrayValue interface {\n\tElements() interface{}\n}\n\ntype ArrayValueEncoder struct {\n\tValues  interface{}\n\tInvalid bool\n}\n\nfunc (a *ArrayValueEncoder) Value() (driver.Value, error) {\n\tif a.Invalid {\n\t\treturn nil, fmt.Errorf(\"cannot use invalid value\")\n\t}\n\n\tb, err := json.Marshal(a.Values)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"json.Marshal failed in %T: %v\", a, err)\n\t}\n\n\treturn driver.Value(string(b)), nil\n}\n\nfunc (a *ArrayValueEncoder) Elements() interface{} {\n\treturn a.Values\n}\n\ntype BoolDecoder struct {\n\tBool *bool\n}\n\nfunc (b *BoolDecoder) UnmarshalJSON(data []byte) error {\n\tif len(data) == 1 {\n\t\tvar v bool\n\t\tif data[0] == '0' {\n\t\t\tv = false\n\t\t\tb.Bool = &v\n\t\t\treturn nil\n\t\t} else if data[0] == '1' {\n\t\t\tv = true\n\t\t\tb.Bool = &v\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tif err := json.Unmarshal(data, &b.Bool); err != nil {\n\t\treturn fmt.Errorf(\"json.Unmarshal failed for bool: %v\", err)\n\t}\n\n\treturn nil\n}\n\ntype ArrayValueDecoder struct {\n\tValues  interface{}\n\tType    ValueType\n\tInvalid bool `json:\"-\"`\n}\n\nfunc (a *ArrayValueDecoder) Value() interface{} {\n\treturn a.Values\n}\n\nfunc (a *ArrayValueDecoder) Scan(src interface{}) error {\n\tif src == nil {\n\t\ta.Invalid = true\n\t\treturn nil\n\t}\n\n\tswitch v := src.(type) {\n\tcase string:\n\t\treturn a.UnmarshalJSON([]byte(v))\n\tcase []uint8:\n\t\tres := make([]int64, len(v))\n\t\tfor i, _ := range v {\n\t\t\tres[i] = int64(v[i])\n\t\t}\n\t\ta.Values = res\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected type %T for %T\", src, a)\n\t}\n\treturn nil\n}\n\nfunc (a *ArrayValueDecoder) UnmarshalJSON(b []byte) error {\n\tif a.Type.ArrayType.Code == TCStruct {\n\t\tvar arr []json.RawMessage\n\t\tif err := json.Unmarshal(b, &arr); err != nil {\n\t\t\treturn fmt.Errorf(\"json.Unmarshal failed in %T: %v\", a, err)\n\t\t}\n\n\t\tvar svs []*StructValue\n\t\tfor _, b2 := range arr {\n\t\t\tvv, err := a.decodeStruct(b2, a.Type.ArrayType.StructType)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tsvs = append(svs, vv)\n\t\t}\n\n\t\ta.Values = svs\n\t} else {\n\t\tv, err := a.decodeValue(b, a.Type)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ta.Values = v\n\t}\n\n\treturn nil\n}\n\nfunc (a *ArrayValueDecoder) decodeStruct(b []byte, typ *StructType) (*StructValue, error) {\n\tvar vv struct {\n\t\tKeys   []string          `json:\"keys\"`\n\t\tValues []json.RawMessage `json:\"values\"`\n\t}\n\n\tif err := json.Unmarshal(b, &vv); err != nil {\n\t\treturn nil, fmt.Errorf(\"json.Unmarshal failed in %T: %v\", a, err)\n\t}\n\n\tvar values []interface{}\n\tfor i, value := range vv.Values {\n\t\ttyp := typ.FieldTypes[i]\n\t\tvvv, err := a.decodeValue(value, *typ)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvalues = append(values, vvv)\n\t}\n\n\treturn &StructValue{\n\t\tKeys:   vv.Keys,\n\t\tValues: values,\n\t}, nil\n}\n\nfunc (a *ArrayValueDecoder) decodeValue(b []byte, typ ValueType) (interface{}, error) {\n\tvar rv reflect.Value\n\tswitch typ.Code {\n\tcase TCBool:\n\t\trv = reflect.New(reflect.TypeOf(BoolDecoder{}))\n\tcase TCInt64:\n\t\trv = reflect.New(reflect.TypeOf(int64(0)))\n\tcase TCFloat64:\n\t\trv = reflect.New(reflect.TypeOf(float64(0)))\n\tcase TCTimestamp, TCDate, TCString:\n\t\trv = reflect.New(reflect.TypeOf(string(\"\")))\n\tcase TCBytes:\n\t\trv = reflect.New(reflect.TypeOf([]byte{}))\n\tcase TCArray:\n\t\tswitch typ.ArrayType.Code {\n\t\tcase TCBool:\n\t\t\trv = reflect.New(reflect.TypeOf([]*BoolDecoder{}))\n\t\tcase TCInt64:\n\t\t\trv = reflect.New(reflect.TypeOf([]*int64{}))\n\t\tcase TCFloat64:\n\t\t\trv = reflect.New(reflect.TypeOf([]*float64{}))\n\t\tcase TCTimestamp, TCDate, TCString:\n\t\t\trv = reflect.New(reflect.TypeOf([]*string{}))\n\t\tcase TCBytes:\n\t\t\trv = reflect.New(reflect.TypeOf([][]byte{}))\n\t\tcase TCStruct:\n\t\t\tv := reflect.New(reflect.TypeOf(ArrayValueDecoder{}))\n\t\t\treflect.Indirect(v).FieldByName(\"Type\").Set(reflect.ValueOf(typ))\n\t\t\trv = v\n\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unknownn supported type for Array: %v\", typ.ArrayType.Code)\n\t\t}\n\tcase TCStruct:\n\t\treturn nil, fmt.Errorf(\"unknown supported type: %v\", typ.Code)\n\t}\n\n\trvv := rv.Interface()\n\tif err := json.Unmarshal([]byte(b), rvv); err != nil {\n\t\treturn nil, fmt.Errorf(\"json.Unmarshalll failed for %T in %T: %v\", rvv, a, err)\n\t}\n\n\tvar value interface{}\n\tswitch vv := rv.Interface().(type) {\n\tcase *BoolDecoder:\n\t\tvalue = *vv.Bool\n\tcase *[]*BoolDecoder:\n\t\tif vv == nil {\n\t\t\tvalue = nil\n\t\t} else {\n\t\t\tvv := *vv\n\t\t\tvs := make([]*bool, len(vv))\n\t\t\tfor i := 0; i < len(vv); i++ {\n\t\t\t\tif vv[i] == nil {\n\t\t\t\t\tvs[i] = nil\n\t\t\t\t} else {\n\t\t\t\t\tvs[i] = vv[i].Bool\n\t\t\t\t}\n\t\t\t}\n\t\t\tvalue = vs\n\t\t}\n\tcase *ArrayValueDecoder:\n\t\tvalue = vv.Value()\n\tdefault:\n\t\tvalue = reflect.Indirect(rv).Interface()\n\t}\n\n\treturn value, nil\n}\n\ntype StructValue struct {\n\tKeys   []string      `json:\"keys\"`\n\tValues []interface{} `json:\"values\"`\n}\n\ntype rows struct {\n\trows        *sql.Rows\n\tresultItems []ResultItem\n\ttransaction *transaction\n\n\tlastErr error\n}\n\nfunc (r *rows) ResultSet() []ResultItem {\n\treturn r.resultItems\n}\n\nfunc (it *rows) Do(fn func([]interface{}) error) error {\n\tvar lastErr error\n\tvar rows []interface{}\n\tfor {\n\t\trow, ok := it.next()\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\t\trows = append(rows, row)\n\t\tif err := fn(row); err != nil {\n\t\t\tlastErr = err\n\t\t\tbreak\n\t\t}\n\t}\n\tif it.lastErr != nil {\n\t\tif lastErr == nil {\n\t\t\tlastErr = it.lastErr\n\t\t}\n\t}\n\n\tif err := it.rows.Err(); err != nil {\n\t\tif lastErr == nil {\n\t\t\tlastErr = err\n\t\t}\n\t}\n\tif err := it.rows.Close(); err != nil {\n\t\tif lastErr == nil {\n\t\t\tlastErr = err\n\t\t}\n\t}\n\n\t// convert sqlite runtime error as InvalidArgument error if it is SqliteArgumentRuntimeError.\n\tif lastErr != nil {\n\t\tmsg := lastErr.Error()\n\t\tif strings.HasPrefix(msg, SqliteArgumentRuntimeErrorPrefix) {\n\t\t\tmsg = strings.TrimPrefix(msg, SqliteArgumentRuntimeErrorPrefix)\n\t\t\treturn status.Errorf(codes.InvalidArgument, \"%s\", msg)\n\t\t}\n\t\tif strings.HasPrefix(msg, SqliteOutOfRangeRuntimeErrorPrefix) {\n\t\t\tmsg = strings.TrimPrefix(msg, SqliteOutOfRangeRuntimeErrorPrefix)\n\t\t\treturn status.Errorf(codes.OutOfRange, \"%s\", msg)\n\t\t}\n\t}\n\n\t// database/sql has a possible bug that cannot read any data without error.\n\t// It may happen context is canceled in bad timing.\n\t// Here checks the transaction is available or not and if it's not available return aborted error.\n\tif !it.transaction.Available() {\n\t\tlastErr = status.Errorf(codes.Aborted, \"transaction aborted\")\n\t}\n\n\treturn lastErr\n}\n\nfunc (it *rows) next() ([]interface{}, bool) {\n\tok := it.rows.Next()\n\tif !ok {\n\t\treturn nil, false\n\t}\n\n\tvalues := make([]reflect.Value, len(it.resultItems))\n\tptrs := make([]interface{}, len(it.resultItems))\n\tfor i, item := range it.resultItems {\n\t\tswitch item.ValueType.Code {\n\t\tcase TCBool:\n\t\t\tvalues[i] = reflect.New(reflect.TypeOf(sql.NullBool{}))\n\t\tcase TCInt64:\n\t\t\tvalues[i] = reflect.New(reflect.TypeOf(sql.NullInt64{}))\n\t\tcase TCFloat64:\n\t\t\tvalues[i] = reflect.New(reflect.TypeOf(sql.NullFloat64{}))\n\t\tcase TCTimestamp, TCDate, TCString, TCJson:\n\t\t\tvalues[i] = reflect.New(reflect.TypeOf(sql.NullString{}))\n\t\tcase TCBytes:\n\t\t\tvalues[i] = reflect.New(reflect.TypeOf(&[]byte{}))\n\t\tcase TCArray:\n\t\t\tv := reflect.New(reflect.TypeOf(ArrayValueDecoder{}))\n\t\t\treflect.Indirect(v).FieldByName(\"Type\").Set(reflect.ValueOf(item.ValueType))\n\t\t\tvalues[i] = v\n\t\tcase TCStruct:\n\t\t\tit.lastErr = fmt.Errorf(\"unknown supported type: %v\", item.ValueType.Code)\n\t\t\treturn nil, false\n\t\t}\n\t\tptrs[i] = values[i].Interface()\n\t}\n\n\tif err := it.rows.Scan(ptrs...); err != nil {\n\t\tit.lastErr = err\n\t\treturn nil, false\n\t}\n\n\tdata := make([]interface{}, len(it.resultItems))\n\n\tfor i := range values {\n\t\tv := reflect.Indirect(values[i]).Interface()\n\t\tswitch vv := v.(type) {\n\t\tcase sql.NullBool:\n\t\t\tif !vv.Valid {\n\t\t\t\tdata[i] = nil\n\t\t\t} else {\n\t\t\t\tdata[i] = vv.Bool\n\t\t\t}\n\n\t\tcase sql.NullString:\n\t\t\tif !vv.Valid {\n\t\t\t\tdata[i] = nil\n\t\t\t} else {\n\t\t\t\tdata[i] = vv.String\n\t\t\t}\n\t\tcase sql.NullInt64:\n\t\t\tif !vv.Valid {\n\t\t\t\tdata[i] = nil\n\t\t\t} else {\n\t\t\t\tdata[i] = vv.Int64\n\t\t\t}\n\t\tcase sql.NullFloat64:\n\t\t\tif !vv.Valid {\n\t\t\t\tdata[i] = nil\n\t\t\t} else {\n\t\t\t\tdata[i] = vv.Float64\n\t\t\t}\n\t\tcase *[]byte:\n\t\t\tif vv == nil {\n\t\t\t\tdata[i] = nil\n\t\t\t} else {\n\t\t\t\tdata[i] = *vv\n\t\t\t}\n\t\tcase ArrayValueDecoder:\n\t\t\tif vv.Invalid {\n\t\t\t\tdata[i] = nil\n\t\t\t} else {\n\t\t\t\tdata[i] = vv.Value()\n\t\t\t}\n\t\tdefault:\n\t\t\tdata[i] = v\n\t\t}\n\t}\n\n\treturn data, true\n}\n\nfunc convertToDatabaseValues(lv *structpb.ListValue, columns []*Column) ([]interface{}, error) {\n\tvalues := make([]interface{}, 0, len(columns))\n\tfor i, v := range lv.Values {\n\t\tcolumn := columns[i]\n\t\tvv, err := spannerValue2DatabaseValue(v, *column)\n\t\tif err != nil {\n\t\t\treturn nil, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t\t}\n\t\tvalues = append(values, vv)\n\t}\n\treturn values, nil\n}\n\nfunc spannerValue2DatabaseValue(v *structpb.Value, col Column) (interface{}, error) {\n\t// special handling of commit_stamp\n\t// It needs to be checked if the column allows to use commit_timestamp\n\tif col.valueType.Code == TCTimestamp {\n\t\tif vv, ok := v.Kind.(*structpb.Value_StringValue); ok {\n\t\t\ts := vv.StringValue\n\t\t\tif s == \"spanner.commit_timestamp()\" {\n\t\t\t\tif !col.allowCommitTimestamp {\n\t\t\t\t\tmsg := \"Cannot write commit timestamp because the allow_commit_timestamp column option is not set to true for column %s, or for all corresponding shared key columns in this table's interleaved table hierarchy.\"\n\t\t\t\t\treturn nil, fmt.Errorf(msg, col.Name) // TODO: return FailedPrecondition\n\t\t\t\t}\n\t\t\t\tnow := time.Now().UTC()\n\t\t\t\tvv.StringValue = now.Format(time.RFC3339Nano)\n\t\t\t}\n\t\t}\n\t}\n\n\tvv, err := makeDataFromSpannerValue(col.Name(), v, col.valueType)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// sqlite doesn not support nil with type like []string(nil)\n\t// explicitly convert those values to nil to store as null value\n\trv := reflect.ValueOf(vv)\n\tif rv.Kind() == reflect.Slice && rv.IsNil() {\n\t\treturn nil, nil\n\t}\n\n\treturn vv, nil\n}\n\nfunc encodeBase64(b []byte) string {\n\treturn base64.StdEncoding.EncodeToString(b)\n}\n\nfunc decodeBase64(s string) ([]byte, error) {\n\tb, err := base64.StdEncoding.DecodeString(s)\n\tif err != nil {\n\t\t// seems Spanner tries to use both padding and no-padding\n\t\treturn base64.RawStdEncoding.DecodeString(s)\n\t}\n\treturn b, nil\n}\n\nfunc makeSpannerTypeFromValueType(typ ValueType) *spannerpb.Type {\n\tvar code spannerpb.TypeCode\n\tswitch typ.Code {\n\tcase TCBool:\n\t\tcode = spannerpb.TypeCode_BOOL\n\tcase TCInt64:\n\t\tcode = spannerpb.TypeCode_INT64\n\tcase TCFloat64:\n\t\tcode = spannerpb.TypeCode_FLOAT64\n\tcase TCTimestamp:\n\t\tcode = spannerpb.TypeCode_TIMESTAMP\n\tcase TCDate:\n\t\tcode = spannerpb.TypeCode_DATE\n\tcase TCString:\n\t\tcode = spannerpb.TypeCode_STRING\n\tcase TCBytes:\n\t\tcode = spannerpb.TypeCode_BYTES\n\tcase TCArray:\n\t\tcode = spannerpb.TypeCode_ARRAY\n\tcase TCStruct:\n\t\tcode = spannerpb.TypeCode_STRUCT\n\tcase TCJson:\n\t\tcode = spannerpb.TypeCode_JSON\n\t}\n\n\tst := &spannerpb.Type{Code: code}\n\tif code == spannerpb.TypeCode_ARRAY {\n\t\tst = &spannerpb.Type{\n\t\t\tCode:             code,\n\t\t\tArrayElementType: makeSpannerTypeFromValueType(*typ.ArrayType),\n\t\t}\n\t}\n\tif code == spannerpb.TypeCode_STRUCT {\n\t\tn := len(typ.StructType.FieldTypes)\n\t\tfields := make([]*spannerpb.StructType_Field, n)\n\t\tfor i := 0; i < n; i++ {\n\t\t\tfields[i] = &spannerpb.StructType_Field{\n\t\t\t\tName: typ.StructType.FieldNames[i],\n\t\t\t\tType: makeSpannerTypeFromValueType(*typ.StructType.FieldTypes[i]),\n\t\t\t}\n\t\t}\n\n\t\tst = &spannerpb.Type{\n\t\t\tCode: code,\n\t\t\tStructType: &spannerpb.StructType{\n\t\t\t\tFields: fields,\n\t\t\t},\n\t\t}\n\t}\n\treturn st\n}\n\nfunc makeValueTypeFromSpannerType(typ *spannerpb.Type) (ValueType, error) {\n\tswitch typ.Code {\n\tcase spannerpb.TypeCode_BOOL:\n\t\treturn ValueType{\n\t\t\tCode: TCBool,\n\t\t}, nil\n\tcase spannerpb.TypeCode_INT64:\n\t\treturn ValueType{\n\t\t\tCode: TCInt64,\n\t\t}, nil\n\tcase spannerpb.TypeCode_FLOAT64:\n\t\treturn ValueType{\n\t\t\tCode: TCFloat64,\n\t\t}, nil\n\tcase spannerpb.TypeCode_TIMESTAMP:\n\t\treturn ValueType{\n\t\t\tCode: TCTimestamp,\n\t\t}, nil\n\tcase spannerpb.TypeCode_DATE:\n\t\treturn ValueType{\n\t\t\tCode: TCDate,\n\t\t}, nil\n\tcase spannerpb.TypeCode_STRING:\n\t\treturn ValueType{\n\t\t\tCode: TCString,\n\t\t}, nil\n\tcase spannerpb.TypeCode_BYTES:\n\t\treturn ValueType{\n\t\t\tCode: TCBytes,\n\t\t}, nil\n\tcase spannerpb.TypeCode_ARRAY:\n\t\tvar array *ValueType\n\t\tif typ.ArrayElementType != nil {\n\t\t\tvt, err := makeValueTypeFromSpannerType(typ.ArrayElementType)\n\t\t\tif err != nil {\n\t\t\t\treturn ValueType{}, err\n\t\t\t}\n\t\t\tarray = &vt\n\t\t}\n\t\treturn ValueType{\n\t\t\tCode:      TCArray,\n\t\t\tArrayType: array,\n\t\t}, nil\n\tcase spannerpb.TypeCode_STRUCT:\n\t\tfields := typ.GetStructType().GetFields()\n\t\tnames := make([]string, len(fields))\n\t\ttypes := make([]*ValueType, len(fields))\n\t\tfor i, field := range fields {\n\t\t\tvt, err := makeValueTypeFromSpannerType(field.Type)\n\t\t\tif err != nil {\n\t\t\t\treturn ValueType{}, err\n\t\t\t}\n\t\t\tnames[i] = field.Name\n\t\t\ttypes[i] = &vt\n\t\t}\n\t\treturn ValueType{\n\t\t\tCode: TCStruct,\n\t\t\tStructType: &StructType{\n\t\t\t\tFieldNames: names,\n\t\t\t\tFieldTypes: types,\n\t\t\t},\n\t\t}, nil\n\tcase spannerpb.TypeCode_JSON:\n\t\treturn ValueType{\n\t\t\tCode: TCJson,\n\t\t}, nil\n\t}\n\n\treturn ValueType{}, fmt.Errorf(\"unknown code for spanner.Type: %v\", typ.Code)\n}\n\nfunc spannerValueFromValue(x interface{}) (*structpb.Value, error) {\n\tswitch x := x.(type) {\n\tcase nil:\n\t\treturn &structpb.Value{Kind: &structpb.Value_NullValue{}}, nil\n\n\tcase bool:\n\t\treturn &structpb.Value{Kind: &structpb.Value_BoolValue{x}}, nil\n\tcase int64:\n\t\t// The Spanner int64 is actually a decimal string.\n\t\ts := strconv.FormatInt(x, 10)\n\t\treturn &structpb.Value{Kind: &structpb.Value_StringValue{s}}, nil\n\tcase float64:\n\t\treturn &structpb.Value{Kind: &structpb.Value_NumberValue{x}}, nil\n\tcase string:\n\t\treturn &structpb.Value{Kind: &structpb.Value_StringValue{x}}, nil\n\tcase []byte:\n\t\tif x == nil {\n\t\t\treturn &structpb.Value{Kind: &structpb.Value_NullValue{}}, nil\n\t\t} else {\n\t\t\treturn &structpb.Value{Kind: &structpb.Value_StringValue{encodeBase64(x)}}, nil\n\t\t}\n\tcase StructValue:\n\t\tn := len(x.Values)\n\t\tfields := make(map[string]*structpb.Value)\n\t\tfor i := 0; i < n; i++ {\n\t\t\telem := x.Values[i]\n\t\t\tv, err := spannerValueFromValue(elem)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tfields[x.Keys[i]] = v\n\t\t}\n\t\treturn &structpb.Value{Kind: &structpb.Value_StructValue{\n\t\t\tStructValue: &structpb.Struct{\n\t\t\t\tFields: fields,\n\t\t\t},\n\t\t}}, nil\n\n\tcase []*bool, []*int64, []*float64, []*string, []*StructValue, [][]byte:\n\t\trv := reflect.ValueOf(x)\n\t\tn := rv.Len()\n\t\tvs := make([]*structpb.Value, n)\n\t\tfor i := 0; i < n; i++ {\n\t\t\tvar elem interface{}\n\n\t\t\trvv := rv.Index(i)\n\t\t\tif !rvv.IsNil() {\n\t\t\t\telem = reflect.Indirect(rv.Index(i)).Interface()\n\t\t\t}\n\n\t\t\tv, err := spannerValueFromValue(elem)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tvs[i] = v\n\t\t}\n\t\treturn &structpb.Value{Kind: &structpb.Value_ListValue{\n\t\t\t&structpb.ListValue{Values: vs},\n\t\t}}, nil\n\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown database value type %T\", x)\n\t}\n}\n\nfunc makeDataFromSpannerValue(key string, v *structpb.Value, typ ValueType) (interface{}, error) {\n\tif typ.StructType != nil {\n\t\treturn nil, newBindingErrorf(key, typ, \"Struct type is not supported yet\")\n\t}\n\n\tif typ.Code == TCArray {\n\t\tif typ.ArrayType == nil {\n\t\t\treturn nil, status.Error(codes.InvalidArgument, \"The array_element_type field is required for ARRAYs\")\n\t\t}\n\n\t\tif _, ok := v.Kind.(*structpb.Value_NullValue); ok {\n\t\t\tswitch typ.ArrayType.Code {\n\t\t\tcase TCBool:\n\t\t\t\treturn []bool(nil), nil\n\t\t\tcase TCInt64:\n\t\t\t\treturn []int64(nil), nil\n\t\t\tcase TCFloat64:\n\t\t\t\treturn []float64(nil), nil\n\t\t\tcase TCTimestamp, TCDate, TCString:\n\t\t\t\treturn []string(nil), nil\n\t\t\tcase TCBytes:\n\t\t\t\treturn [][]byte(nil), nil\n\t\t\tcase TCArray, TCStruct:\n\t\t\t\t// this should not be error actually but no reason to support.\n\t\t\t\treturn nil, newBindingErrorf(key, typ, \"nested Array or Struct for Array is not supported yet\")\n\t\t\tdefault:\n\t\t\t\treturn nil, fmt.Errorf(\"unexpected type %d for Null value as Array\", typ.ArrayType.Code)\n\t\t\t}\n\t\t}\n\n\t\tvv, ok := v.Kind.(*structpb.Value_ListValue)\n\t\tif !ok {\n\t\t\treturn nil, newBindingErrorf(key, typ, \"unexpected value %T and type %s as Array\", v.Kind, typ)\n\t\t}\n\n\t\tn := len(vv.ListValue.Values)\n\t\tswitch typ.ArrayType.Code {\n\t\tcase TCBool:\n\t\t\tret := make([]*bool, n)\n\t\t\tfor i, vv := range vv.ListValue.Values {\n\t\t\t\telemKey := fmt.Sprintf(\"%s[%d]\", key, i)\n\t\t\t\tvvv, err := makeDataFromSpannerValue(elemKey, vv, *typ.ArrayType)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif vvv == nil {\n\t\t\t\t\tret[i] = nil\n\t\t\t\t} else {\n\t\t\t\t\tvvvv, ok := vvv.(bool)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tpanic(fmt.Sprintf(\"unexpected value type: %T\", vvv))\n\t\t\t\t\t}\n\t\t\t\t\tret[i] = &vvvv\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn &ArrayValueEncoder{Values: ret}, nil\n\t\tcase TCInt64:\n\t\t\tret := make([]*int64, n)\n\t\t\tfor i, vv := range vv.ListValue.Values {\n\t\t\t\telemKey := fmt.Sprintf(\"%s[%d]\", key, i)\n\t\t\t\tvvv, err := makeDataFromSpannerValue(elemKey, vv, *typ.ArrayType)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif vvv == nil {\n\t\t\t\t\tret[i] = nil\n\t\t\t\t} else {\n\t\t\t\t\tvvvv, ok := vvv.(int64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tpanic(fmt.Sprintf(\"unexpected value type: %T\", vvv))\n\t\t\t\t\t}\n\t\t\t\t\tret[i] = &vvvv\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn &ArrayValueEncoder{Values: ret}, nil\n\t\tcase TCFloat64:\n\t\t\tret := make([]*float64, n)\n\t\t\tfor i, vv := range vv.ListValue.Values {\n\t\t\t\telemKey := fmt.Sprintf(\"%s[%d]\", key, i)\n\t\t\t\tvvv, err := makeDataFromSpannerValue(elemKey, vv, *typ.ArrayType)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif vvv == nil {\n\t\t\t\t\tret[i] = nil\n\t\t\t\t} else {\n\t\t\t\t\tvvvv, ok := vvv.(float64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tpanic(fmt.Sprintf(\"unexpected value type: %T\", vvv))\n\t\t\t\t\t}\n\t\t\t\t\tret[i] = &vvvv\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn &ArrayValueEncoder{Values: ret}, nil\n\t\tcase TCTimestamp, TCDate, TCString:\n\t\t\tret := make([]*string, n)\n\t\t\tfor i, vv := range vv.ListValue.Values {\n\t\t\t\telemKey := fmt.Sprintf(\"%s[%d]\", key, i)\n\t\t\t\tvvv, err := makeDataFromSpannerValue(elemKey, vv, *typ.ArrayType)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif vvv == nil {\n\t\t\t\t\tret[i] = nil\n\t\t\t\t} else {\n\t\t\t\t\tvvvv, ok := vvv.(string)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tpanic(fmt.Sprintf(\"unexpected value type: %T\", vvv))\n\t\t\t\t\t}\n\t\t\t\t\tret[i] = &vvvv\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn &ArrayValueEncoder{Values: ret}, nil\n\t\tcase TCBytes:\n\t\t\tret := make([][]byte, n)\n\t\t\tfor i, vv := range vv.ListValue.Values {\n\t\t\t\telemKey := fmt.Sprintf(\"%s[%d]\", key, i)\n\t\t\t\tvvv, err := makeDataFromSpannerValue(elemKey, vv, *typ.ArrayType)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif vvv == nil {\n\t\t\t\t\tret[i] = nil\n\t\t\t\t} else {\n\t\t\t\t\tvvvv, ok := vvv.([]byte)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tpanic(fmt.Sprintf(\"unexpected value type: %T\", vvv))\n\t\t\t\t\t}\n\t\t\t\t\tret[i] = vvvv\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn &ArrayValueEncoder{Values: ret}, nil\n\t\tcase TCArray, TCStruct:\n\t\t\t// just visit elements for appropriate error handling\n\t\t\tfor i, vv := range vv.ListValue.Values {\n\t\t\t\telemKey := fmt.Sprintf(\"%s[%d]\", key, i)\n\t\t\t\t_, err := makeDataFromSpannerValue(elemKey, vv, *typ.ArrayType)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// must be unreachable\n\t\t\tpanic(\"array of array or array of struct is not supported\")\n\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unknown TypeCode for ArrayElement %v\", typ.Code)\n\t\t}\n\t}\n\n\tif _, ok := v.Kind.(*structpb.Value_NullValue); ok {\n\t\treturn nil, nil\n\t}\n\n\tswitch typ.Code {\n\tcase TCBool:\n\t\tswitch vv := v.Kind.(type) {\n\t\tcase *structpb.Value_BoolValue:\n\t\t\treturn vv.BoolValue, nil\n\t\t}\n\tcase TCInt64:\n\t\tswitch vv := v.Kind.(type) {\n\t\tcase *structpb.Value_StringValue:\n\t\t\t// base is always 10\n\t\t\tn, err := strconv.ParseInt(vv.StringValue, 10, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, newBindingErrorf(key, typ, \"unexpected format %q as int64: %v\", vv.StringValue, err)\n\t\t\t}\n\t\t\treturn n, nil\n\t\t}\n\n\tcase TCFloat64:\n\t\tswitch vv := v.Kind.(type) {\n\t\tcase *structpb.Value_NumberValue:\n\t\t\treturn vv.NumberValue, nil\n\t\t}\n\n\tcase TCTimestamp:\n\t\tswitch vv := v.Kind.(type) {\n\t\tcase *structpb.Value_StringValue:\n\t\t\ts := vv.StringValue\n\t\t\tif _, err := time.Parse(time.RFC3339Nano, s); err != nil {\n\t\t\t\treturn nil, newBindingErrorf(key, typ, \"unexpected format %q as timestamp: %v\", s, err)\n\t\t\t}\n\t\t\treturn s, nil\n\t\t}\n\n\tcase TCDate:\n\t\tswitch vv := v.Kind.(type) {\n\t\tcase *structpb.Value_StringValue:\n\t\t\ts := vv.StringValue\n\t\t\tif _, err := time.Parse(\"2006-01-02\", s); err != nil {\n\t\t\t\treturn nil, newBindingErrorf(key, typ, \"unexpected format for %q as date: %v\", s, err)\n\t\t\t}\n\t\t\treturn s, nil\n\t\t}\n\n\tcase TCString, TCJson:\n\t\tswitch vv := v.Kind.(type) {\n\t\tcase *structpb.Value_StringValue:\n\t\t\treturn vv.StringValue, nil\n\t\t}\n\tcase TCBytes:\n\t\tswitch vv := v.Kind.(type) {\n\t\tcase *structpb.Value_StringValue:\n\t\t\tb, err := decodeBase64(vv.StringValue)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, newBindingErrorf(key, typ, \"decoding base64 failed: %v\", err)\n\t\t\t}\n\t\t\treturn b, nil\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown Type %s\", typ)\n\t}\n\n\treturn nil, newBindingErrorf(key, typ, \"unexpected value %T and type %s\", v.Kind, typ)\n}\n\nfunc makeValueFromSpannerValue(key string, v *structpb.Value, typ *spannerpb.Type) (Value, error) {\n\tvt, err := makeValueTypeFromSpannerType(typ)\n\tif err != nil {\n\t\treturn Value{}, err\n\t}\n\n\tdata, err := makeDataFromSpannerValue(key, v, vt)\n\tif err != nil {\n\t\treturn Value{}, err\n\t}\n\n\treturn Value{\n\t\tData: data,\n\t\tType: vt,\n\t}, nil\n}\n\ntype bindingError struct {\n\tkey      string\n\tmsg      string\n\texpected ValueType\n}\n\nfunc (e *bindingError) Error() string {\n\treturn e.msg\n}\n\nfunc (e *bindingError) GRPCStatus() *status.Status {\n\treturn status.Newf(codes.InvalidArgument, \"Invalid value for bind parameter %s: Expected %s.\", e.key, e.expected)\n}\n\nfunc newBindingErrorf(key string, expected ValueType, format string, a ...interface{}) error {\n\treturn &bindingError{\n\t\tkey:      key,\n\t\texpected: expected,\n\t\tmsg:      fmt.Sprintf(format, a...),\n\t}\n}\n"
  },
  {
    "path": "server/value_test.go",
    "content": "// Copyright 2019 Masahiro Sano\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"testing\"\n\t\"time\"\n\n\t\"database/sql\"\n\n\t\"github.com/cloudspannerecosystem/memefish/ast\"\n\tcmp \"github.com/google/go-cmp/cmp\"\n\tuuidpkg \"github.com/google/uuid\"\n\tspannerpb \"google.golang.org/genproto/googleapis/spanner/v1\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tstructpb \"google.golang.org/protobuf/types/known/structpb\"\n)\n\nfunc TestCompareValueType(t *testing.T) {\n\ttable := []struct {\n\t\ta      ValueType\n\t\tb      ValueType\n\t\tresult bool\n\t}{\n\t\t{\n\t\t\ta:      ValueType{Code: TCInt64},\n\t\t\tb:      ValueType{Code: TCInt64},\n\t\t\tresult: true,\n\t\t},\n\t\t{\n\t\t\ta:      ValueType{Code: TCString},\n\t\t\tb:      ValueType{Code: TCString},\n\t\t\tresult: true,\n\t\t},\n\t\t{\n\t\t\ta:      ValueType{Code: TCInt64},\n\t\t\tb:      ValueType{Code: TCString},\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\ta: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &ValueType{Code: TCInt64},\n\t\t\t},\n\t\t\tb: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &ValueType{Code: TCInt64},\n\t\t\t},\n\t\t\tresult: true,\n\t\t},\n\t\t{\n\t\t\ta: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &ValueType{Code: TCString},\n\t\t\t},\n\t\t\tb: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &ValueType{Code: TCString},\n\t\t\t},\n\t\t\tresult: true,\n\t\t},\n\t\t{\n\t\t\ta: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &ValueType{Code: TCString},\n\t\t\t},\n\t\t\tb: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &ValueType{Code: TCInt64},\n\t\t\t},\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\ta: ValueType{Code: TCInt64},\n\t\t\tb: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &ValueType{Code: TCInt64},\n\t\t\t},\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\ta: ValueType{\n\t\t\t\tCode: TCStruct,\n\t\t\t\tStructType: &StructType{\n\t\t\t\t\tFieldNames: []string{\"\", \"\", \"\"},\n\t\t\t\t\tFieldTypes: []*ValueType{\n\t\t\t\t\t\t&ValueType{Code: TCInt64},\n\t\t\t\t\t\t&ValueType{Code: TCString},\n\t\t\t\t\t},\n\t\t\t\t\tIsTable: false,\n\t\t\t\t},\n\t\t\t},\n\t\t\tb: ValueType{\n\t\t\t\tCode: TCStruct,\n\t\t\t\tStructType: &StructType{\n\t\t\t\t\tFieldNames: []string{\"\", \"\", \"\"},\n\t\t\t\t\tFieldTypes: []*ValueType{\n\t\t\t\t\t\t&ValueType{Code: TCInt64},\n\t\t\t\t\t\t&ValueType{Code: TCString},\n\t\t\t\t\t},\n\t\t\t\t\tIsTable: false,\n\t\t\t\t},\n\t\t\t},\n\t\t\tresult: true,\n\t\t},\n\t\t{\n\t\t\ta: ValueType{\n\t\t\t\tCode: TCStruct,\n\t\t\t\tStructType: &StructType{\n\t\t\t\t\tFieldNames: []string{\"\", \"\", \"\"},\n\t\t\t\t\tFieldTypes: []*ValueType{\n\t\t\t\t\t\t&ValueType{Code: TCInt64},\n\t\t\t\t\t\t&ValueType{Code: TCString},\n\t\t\t\t\t},\n\t\t\t\t\tIsTable: false,\n\t\t\t\t},\n\t\t\t},\n\t\t\tb: ValueType{\n\t\t\t\tCode: TCStruct,\n\t\t\t\tStructType: &StructType{\n\t\t\t\t\tFieldNames: []string{\"a\", \"b\", \"c\"},\n\t\t\t\t\tFieldTypes: []*ValueType{\n\t\t\t\t\t\t&ValueType{Code: TCInt64},\n\t\t\t\t\t\t&ValueType{Code: TCString},\n\t\t\t\t\t},\n\t\t\t\t\tIsTable: true,\n\t\t\t\t},\n\t\t\t},\n\t\t\tresult: true,\n\t\t},\n\t\t{\n\t\t\ta: ValueType{\n\t\t\t\tCode: TCStruct,\n\t\t\t\tStructType: &StructType{\n\t\t\t\t\tFieldNames: []string{\"\", \"\", \"\"},\n\t\t\t\t\tFieldTypes: []*ValueType{\n\t\t\t\t\t\t&ValueType{Code: TCInt64},\n\t\t\t\t\t\t&ValueType{Code: TCString},\n\t\t\t\t\t},\n\t\t\t\t\tIsTable: false,\n\t\t\t\t},\n\t\t\t},\n\t\t\tb: ValueType{\n\t\t\t\tCode: TCStruct,\n\t\t\t\tStructType: &StructType{\n\t\t\t\t\tFieldNames: []string{\"\", \"\", \"\"},\n\t\t\t\t\tFieldTypes: []*ValueType{\n\t\t\t\t\t\t&ValueType{Code: TCString},\n\t\t\t\t\t\t&ValueType{Code: TCInt64},\n\t\t\t\t\t},\n\t\t\t\t\tIsTable: false,\n\t\t\t\t},\n\t\t\t},\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\ta: ValueType{\n\t\t\t\tCode: TCStruct,\n\t\t\t\tStructType: &StructType{\n\t\t\t\t\tFieldNames: []string{\"\", \"\", \"\"},\n\t\t\t\t\tFieldTypes: []*ValueType{\n\t\t\t\t\t\t&ValueType{Code: TCInt64},\n\t\t\t\t\t\t&ValueType{\n\t\t\t\t\t\t\tCode:      TCArray,\n\t\t\t\t\t\t\tArrayType: &ValueType{Code: TCInt64},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tIsTable: false,\n\t\t\t\t},\n\t\t\t},\n\t\t\tb: ValueType{\n\t\t\t\tCode: TCStruct,\n\t\t\t\tStructType: &StructType{\n\t\t\t\t\tFieldNames: []string{\"\", \"\", \"\"},\n\t\t\t\t\tFieldTypes: []*ValueType{\n\t\t\t\t\t\t&ValueType{Code: TCInt64},\n\t\t\t\t\t\t&ValueType{\n\t\t\t\t\t\t\tCode:      TCArray,\n\t\t\t\t\t\t\tArrayType: &ValueType{Code: TCInt64},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tIsTable: false,\n\t\t\t\t},\n\t\t\t},\n\t\t\tresult: true,\n\t\t},\n\t}\n\n\tfor _, tc := range table {\n\t\tr := compareValueType(tc.a, tc.b)\n\t\tif r != tc.result {\n\t\t\tt.Errorf(\"expect result %v, but got %v\", tc.result, r)\n\t\t}\n\t}\n}\n\nfunc TestDatabaseEncDec(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\tdefer cancel()\n\n\ttable := map[string]struct {\n\t\tvalue    *structpb.Value\n\t\ttyp      ValueType\n\t\texpected interface{}\n\t}{\n\t\t\"Bool\": {\n\t\t\tvalue:    makeBoolValue(true),\n\t\t\ttyp:      ValueType{Code: TCBool},\n\t\t\texpected: true,\n\t\t},\n\t\t\"Int\": {\n\t\t\tvalue:    makeStringValue(\"100\"),\n\t\t\ttyp:      ValueType{Code: TCInt64},\n\t\t\texpected: int64(100),\n\t\t},\n\t\t\"Float\": {\n\t\t\tvalue:    makeNumberValue(0.5),\n\t\t\ttyp:      ValueType{Code: TCFloat64},\n\t\t\texpected: float64(0.5),\n\t\t},\n\t\t\"Bytes\": {\n\t\t\tvalue:    makeStringValue(\"eHh4\"), // xxx\n\t\t\ttyp:      ValueType{Code: TCBytes},\n\t\t\texpected: []byte(\"xxx\"),\n\t\t},\n\t\t\"Timestamp\": {\n\t\t\tvalue:    makeStringValue(\"2012-03-04T00:00:00.123456789Z\"),\n\t\t\ttyp:      ValueType{Code: TCTimestamp},\n\t\t\texpected: \"2012-03-04T00:00:00.123456789Z\",\n\t\t},\n\t\t\"ArrayBool\": {\n\t\t\tvalue: makeListValueAsValue(makeListValue(\n\t\t\t\tmakeBoolValue(true),\n\t\t\t\tmakeBoolValue(false),\n\t\t\t)),\n\t\t\ttyp: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &ValueType{Code: TCBool},\n\t\t\t},\n\t\t\texpected: makeTestArray(TCBool, true, false),\n\t\t},\n\t\t\"ArrayString\": {\n\t\t\tvalue: makeListValueAsValue(makeListValue(\n\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t)),\n\t\t\ttyp: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &ValueType{Code: TCString},\n\t\t\t},\n\t\t\texpected: makeTestArray(TCString, \"xxx\", \"yyy\"),\n\t\t},\n\t\t\"ArrayInt\": {\n\t\t\tvalue: makeListValueAsValue(makeListValue(\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"200\"),\n\t\t\t)),\n\t\t\ttyp: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &ValueType{Code: TCInt64},\n\t\t\t},\n\t\t\texpected: makeTestArray(TCInt64, 100, 200),\n\t\t},\n\t\t\"ArrayFloat\": {\n\t\t\tvalue: makeListValueAsValue(makeListValue(\n\t\t\t\tmakeNumberValue(0.1),\n\t\t\t\tmakeNumberValue(0.2),\n\t\t\t)),\n\t\t\ttyp: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &ValueType{Code: TCFloat64},\n\t\t\t},\n\t\t\texpected: makeTestArray(TCFloat64, float64(0.1), float64(0.2)),\n\t\t},\n\t\t\"ArrayBytes\": {\n\t\t\tvalue: makeListValueAsValue(makeListValue(\n\t\t\t\tmakeStringValue(\"eHl6\"), // xyz\n\t\t\t\tmakeStringValue(\"eHh4\"), // xxx\n\t\t\t)),\n\t\t\ttyp: ValueType{\n\t\t\t\tCode:      TCArray,\n\t\t\t\tArrayType: &ValueType{Code: TCBytes},\n\t\t\t},\n\t\t\texpected: makeTestArray(TCBytes, []byte(\"xyz\"), []byte(\"xxx\")),\n\t\t},\n\t\t\"JSON\": {\n\t\t\tvalue: makeStringValue(`{\"a\": 1, \"b\": 2}`),\n\t\t\ttyp: ValueType{\n\t\t\t\tCode: TCJson,\n\t\t\t},\n\t\t\texpected: `{\"a\": 1, \"b\": 2}`,\n\t\t},\n\t}\n\n\tuuid := uuidpkg.New().String()\n\tdb, err := sql.Open(\"sqlite3_spanner\", fmt.Sprintf(\"file:%s.db?cache=shared&mode=memory\", uuid))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer db.Close()\n\n\tif _, err := db.ExecContext(ctx, \"CREATE TABLE test (js JSON)\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor name, tc := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif _, err := db.ExecContext(ctx, \"DELETE FROM test\"); err != nil {\n\t\t\t\t\tt.Fatalf(\"delete failed: %v\", err)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tcolumn := Column{\n\t\t\t\tast:        &ast.ColumnDef{Name: &ast.Ident{Name: \"Id\"}},\n\t\t\t\tvalueType:  tc.typ,\n\t\t\t\tdbDataType: DBDTJson,\n\t\t\t}\n\n\t\t\tv, err := spannerValue2DatabaseValue(tc.value, column)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"spannerValue2DatabaseValue failed: %v\", err)\n\t\t\t}\n\n\t\t\tif _, err := db.ExecContext(ctx, \"INSERT INTO test VALUES(?)\", v); err != nil {\n\t\t\t\tt.Fatalf(\"insert failed: %v\", err)\n\t\t\t}\n\n\t\t\tr, err := db.QueryContext(ctx, \"SELECT js FROM test\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"select failed: %v\", err)\n\t\t\t}\n\t\t\tdefer r.Close()\n\n\t\t\titem := createResultItemFromColumn(&column)\n\t\t\titer := rows{rows: r, resultItems: []ResultItem{item}, transaction: &transaction{status: 1}}\n\n\t\t\tvar rows [][]interface{}\n\t\t\terr = iter.Do(func(row []interface{}) error {\n\t\t\t\trows = append(rows, row)\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error in iteration: %v\", err)\n\t\t\t}\n\n\t\t\tif len(rows) != 1 {\n\t\t\t\tt.Errorf(\"there should be only 1 row\")\n\t\t\t}\n\t\t\tif diff := cmp.Diff(tc.expected, rows[0][0]); diff != \"\" {\n\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMakeValueFromSpannerValue(t *testing.T) {\n\ttable := map[string]struct {\n\t\tvalue    *structpb.Value\n\t\ttyp      *spannerpb.Type\n\t\texpected Value\n\t}{\n\t\t\"Null\": {\n\t\t\tvalue: makeNullValue(),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: nil,\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCInt64,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Int\": {\n\t\t\tvalue: makeStringValue(\"100\"),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: int64(100),\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCInt64,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"String\": {\n\t\t\tvalue: makeStringValue(\"xx\"),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_STRING,\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: \"xx\",\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCString,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Bool\": {\n\t\t\tvalue: makeBoolValue(true),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_BOOL,\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: true,\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCBool,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Number\": {\n\t\t\tvalue: makeNumberValue(0.123),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_FLOAT64,\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: 0.123,\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCFloat64,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Timestamp\": {\n\t\t\tvalue: makeStringValue(\"2012-03-04T00:00:00.123456789Z\"),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_TIMESTAMP,\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: \"2012-03-04T00:00:00.123456789Z\",\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCTimestamp,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Date\": {\n\t\t\tvalue: makeStringValue(\"2012-03-04\"),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_DATE,\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: \"2012-03-04\",\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCDate,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Bytes\": {\n\t\t\tvalue: makeStringValue(\"eHh4eHg=\"),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_BYTES,\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: []byte(\"xxxxx\"),\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCBytes,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ListInt\": {\n\t\t\tvalue: makeListValueAsValue(makeListValue(\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"101\"),\n\t\t\t)),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: makeTestWrappedArray(TCInt64, 100, 101),\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCArray,\n\t\t\t\t\tArrayType: &ValueType{\n\t\t\t\t\t\tCode: TCInt64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ListString\": {\n\t\t\tvalue: makeListValueAsValue(makeListValue(\n\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t\tmakeStringValue(\"yyy\"),\n\t\t\t)),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_STRING,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: makeTestWrappedArray(TCString, \"xxx\", \"yyy\"),\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCArray,\n\t\t\t\t\tArrayType: &ValueType{\n\t\t\t\t\t\tCode: TCString,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ListStringNull\": {\n\t\t\tvalue: makeNullValue(),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_STRING,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: []string(nil),\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCArray,\n\t\t\t\t\tArrayType: &ValueType{\n\t\t\t\t\t\tCode: TCString,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ListBool\": {\n\t\t\tvalue: makeListValueAsValue(makeListValue(\n\t\t\t\tmakeBoolValue(true),\n\t\t\t\tmakeBoolValue(false),\n\t\t\t)),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_BOOL,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: makeTestWrappedArray(TCBool, true, false),\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCArray,\n\t\t\t\t\tArrayType: &ValueType{\n\t\t\t\t\t\tCode: TCBool,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ListBoolNull\": {\n\t\t\tvalue: makeNullValue(),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_BOOL,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: []bool(nil),\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCArray,\n\t\t\t\t\tArrayType: &ValueType{\n\t\t\t\t\t\tCode: TCBool,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ListNumber\": {\n\t\t\tvalue: makeListValueAsValue(makeListValue(\n\t\t\t\tmakeNumberValue(0.123),\n\t\t\t\tmakeNumberValue(1.123),\n\t\t\t)),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_FLOAT64,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: makeTestWrappedArray(TCFloat64, 0.123, 1.123),\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCArray,\n\t\t\t\t\tArrayType: &ValueType{\n\t\t\t\t\t\tCode: TCFloat64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ListNumberNull\": {\n\t\t\tvalue: makeNullValue(),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_FLOAT64,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: []float64(nil),\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCArray,\n\t\t\t\t\tArrayType: &ValueType{\n\t\t\t\t\t\tCode: TCFloat64,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ListTimestamp\": {\n\t\t\tvalue: makeListValueAsValue(makeListValue(\n\t\t\t\tmakeStringValue(\"2012-03-04T00:00:00.123456789Z\"),\n\t\t\t\tmakeStringValue(\"2012-03-04T00:00:00.000000000Z\"),\n\t\t\t)),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_TIMESTAMP,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: makeTestWrappedArray(TCString,\n\t\t\t\t\t\"2012-03-04T00:00:00.123456789Z\",\n\t\t\t\t\t\"2012-03-04T00:00:00.000000000Z\",\n\t\t\t\t),\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCArray,\n\t\t\t\t\tArrayType: &ValueType{\n\t\t\t\t\t\tCode: TCTimestamp,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ListTimestampNull\": {\n\t\t\tvalue: makeNullValue(),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_TIMESTAMP,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: []string(nil),\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCArray,\n\t\t\t\t\tArrayType: &ValueType{\n\t\t\t\t\t\tCode: TCTimestamp,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ListDate\": {\n\t\t\tvalue: makeListValueAsValue(makeListValue(\n\t\t\t\tmakeStringValue(\"2012-03-04\"),\n\t\t\t\tmakeStringValue(\"2012-03-05\"),\n\t\t\t)),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_DATE,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: makeTestWrappedArray(TCString, \"2012-03-04\", \"2012-03-05\"),\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCArray,\n\t\t\t\t\tArrayType: &ValueType{\n\t\t\t\t\t\tCode: TCDate,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ListDateNull\": {\n\t\t\tvalue: makeNullValue(),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_DATE,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: []string(nil),\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCArray,\n\t\t\t\t\tArrayType: &ValueType{\n\t\t\t\t\t\tCode: TCDate,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ListBytes\": {\n\t\t\tvalue: makeListValueAsValue(makeListValue(\n\t\t\t\tmakeStringValue(\"eHh4eHg=\"),\n\t\t\t\tmakeStringValue(\"eXl5eXk=\"),\n\t\t\t)),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_BYTES,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: makeTestWrappedArray(TCBytes, []byte(\"xxxxx\"), []byte(\"yyyyy\")),\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCArray,\n\t\t\t\t\tArrayType: &ValueType{\n\t\t\t\t\t\tCode: TCBytes,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"ListBytesNull\": {\n\t\t\tvalue: makeNullValue(),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_BYTES,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: [][]byte(nil),\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCArray,\n\t\t\t\t\tArrayType: &ValueType{\n\t\t\t\t\t\tCode: TCBytes,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"JSON\": {\n\t\t\tvalue: makeStringValue(`{\"a\": 1, \"b\": 2}`),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_JSON,\n\t\t\t},\n\t\t\texpected: Value{\n\t\t\t\tData: `{\"a\": 1, \"b\": 2}`,\n\t\t\t\tType: ValueType{\n\t\t\t\t\tCode: TCJson,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tc := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tres, err := makeValueFromSpannerValue(\"foo\", tc.value, tc.typ)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif diff := cmp.Diff(tc.expected, res); diff != \"\" {\n\t\t\t\tt.Errorf(\"(-got, +want)\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n\n}\n\nfunc TestMakeValueFromSpannerValue_Error(t *testing.T) {\n\ttable := map[string]struct {\n\t\tvalue *structpb.Value\n\t\ttyp   *spannerpb.Type\n\t\tmsg   *regexp.Regexp\n\t}{\n\t\t\"Int_InvalidValue\": {\n\t\t\tvalue: makeStringValue(\"xxx\"),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t},\n\t\t\tmsg: regexp.MustCompile(`^Invalid value for bind parameter foo: Expected INT64`),\n\t\t},\n\t\t\"Int_InvalidType\": {\n\t\t\tvalue: makeBoolValue(true),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t},\n\t\t\tmsg: regexp.MustCompile(`^Invalid value for bind parameter foo: Expected INT64`),\n\t\t},\n\n\t\t\"Timestamp_InvalidValue\": {\n\t\t\tvalue: makeStringValue(\"xxx\"),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_TIMESTAMP,\n\t\t\t},\n\t\t\tmsg: regexp.MustCompile(`^Invalid value for bind parameter foo: Expected TIMESTAMP`),\n\t\t},\n\t\t\"Timestamp_InvalidType\": {\n\t\t\tvalue: makeBoolValue(true),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_TIMESTAMP,\n\t\t\t},\n\t\t\tmsg: regexp.MustCompile(`^Invalid value for bind parameter foo: Expected TIMESTAMP`),\n\t\t},\n\n\t\t\"Date_InvalidValue\": {\n\t\t\tvalue: makeStringValue(\"xxx\"),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_DATE,\n\t\t\t},\n\t\t\tmsg: regexp.MustCompile(`^Invalid value for bind parameter foo: Expected DATE`),\n\t\t},\n\t\t\"Date_InvalidType\": {\n\t\t\tvalue: makeBoolValue(true),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_DATE,\n\t\t\t},\n\t\t\tmsg: regexp.MustCompile(`^Invalid value for bind parameter foo: Expected DATE`),\n\t\t},\n\n\t\t\"Bytes_InvalidValue\": {\n\t\t\tvalue: makeStringValue(\"x\"),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_BYTES,\n\t\t\t},\n\t\t\tmsg: regexp.MustCompile(`^Invalid value for bind parameter foo: Expected BYTES`),\n\t\t},\n\t\t\"Bytes_InvalidType\": {\n\t\t\tvalue: makeBoolValue(true),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_BYTES,\n\t\t\t},\n\t\t\tmsg: regexp.MustCompile(`^Invalid value for bind parameter foo: Expected BYTES`),\n\t\t},\n\n\t\t\"Array_InvalidType\": {\n\t\t\tvalue: makeStringValue(\"xxx\"),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_STRING,\n\t\t\t\t},\n\t\t\t},\n\t\t\tmsg: regexp.MustCompile(`^Invalid value for bind parameter foo: Expected ARRAY<STRING>`),\n\t\t},\n\t\t\"Array_InvalidElementValue\": {\n\t\t\tvalue: makeListValueAsValue(makeListValue(\n\t\t\t\tmakeStringValue(\"100\"),\n\t\t\t\tmakeStringValue(\"xxx\"),\n\t\t\t)),\n\t\t\ttyp: &spannerpb.Type{\n\t\t\t\tCode: spannerpb.TypeCode_ARRAY,\n\t\t\t\tArrayElementType: &spannerpb.Type{\n\t\t\t\t\tCode: spannerpb.TypeCode_INT64,\n\t\t\t\t},\n\t\t\t},\n\t\t\tmsg: regexp.MustCompile(`^Invalid value for bind parameter foo\\[1\\]: Expected INT64`),\n\t\t},\n\t}\n\n\tfor name, tc := range table {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\t_, err := makeValueFromSpannerValue(\"foo\", tc.value, tc.typ)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatalf(\"unexpected success\")\n\t\t\t}\n\t\t\tst := status.Convert(err)\n\t\t\tif st.Code() != codes.InvalidArgument {\n\t\t\t\tt.Errorf(\"expect code to be %v, but got %v\", codes.InvalidArgument, st.Code())\n\t\t\t}\n\t\t\tif !tc.msg.MatchString(st.Message()) {\n\t\t\t\tt.Errorf(\"unexpected error message: \\n %q\\n expected:\\n %q\", st.Message(), tc.msg)\n\t\t\t}\n\t\t})\n\t}\n\n}\n"
  }
]