[
  {
    "path": ".github/release.yml",
    "content": "changelog:\n  exclude:\n    labels:\n      - ignore-for-release\n  categories:\n    - title: Breaking changes\n      labels:\n        - breaking-change\n    - title: Features\n      labels:\n        - enhancement\n    - title: Bug fixes\n      labels:\n        - bug\n    - title: Other changes\n      labels:\n        - \"*\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: ci\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        go: ['>=1.25','1.24.7']\n        trino: ['latest', '372']\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-go@v6\n        with:\n          go-version: ${{ matrix.go }}\n      - run: go test -v -race -timeout 2m ./... -trino_image_tag=${{ matrix.trino }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: release\n\non:\n  push:\n    # run only against tags\n    tags:\n      - '*'\n\npermissions:\n  contents: write\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Fetch all tags\n        run: git fetch --force --tags\n\n      - name: Set up Go\n        uses: actions/setup-go@v6\n        with:\n          go-version: \"1.25\"\n\n      - name: Run GoReleaser\n        uses: goreleaser/goreleaser-action@v6\n        with:\n          distribution: goreleaser\n          version: latest\n          args: release --clean\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "coverage.out\n.idea\n/dist\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "builds:\n- skip: true\nchangelog:\n  use: github-native\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Trino\n\n## Contributor License Agreement (\"CLA\")\n\nIn order to accept your pull request, we need you to [submit a CLA](https://github.com/trinodb/cla).\n\n## License\n\nBy contributing to Trino, you agree that your contributions will be licensed under the [Apache License Version 2.0 (APLv2)](LICENSE).\n\n# Go Test\n\nPlease Run [go test](https://pkg.go.dev/testing) before creating Pull Request\n\n```bash\ngo test -v -race -timeout 1m ./...\n```\n\n# Releases\n\nTo create a new release, a maintainer with repository write permissions needs to create and push a new git tag.\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 [yyyy] [name of copyright owner]\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": "README.md",
    "content": "# Trino Go client\n\nA [Trino](https://trino.io) client for the [Go](https://golang.org) programming\nlanguage. It enables you to send SQL statements from your Go application to\nTrino, and receive the resulting data.\n\n[![Build Status](https://github.com/trinodb/trino-go-client/workflows/ci/badge.svg)](https://github.com/trinodb/trino-go-client/actions?query=workflow%3Aci+event%3Apush+branch%3Amaster)\n[![GoDoc](https://godoc.org/github.com/trinodb/trino-go-client?status.svg)](https://godoc.org/github.com/trinodb/trino-go-client)\n\n## Features\n\n* Native Go implementation\n* Connections over HTTP or HTTPS\n* HTTP Basic, Kerberos, and JSON web token (JWT) authentication\n* Per-query user information for access control\n* Support custom HTTP client (tunable conn pools, timeouts, TLS)\n* Supports conversion from Trino to native Go data types\n  * `string`, `sql.NullString`\n  * `int64`, `sql.NullInt64`\n  * `float64`, `sql.NullFloat64`\n  * `map`, `trino.NullMap`\n  * `time.Time`, `trino.NullTime`\n  * Up to 3-dimensional arrays to Go slices, of any supported type\n\n## Requirements\n\n* Go 1.24.7 or newer\n* Trino 372 or newer\n\n## Installation\n\nYou need a working environment with Go installed and $GOPATH set.\n\nDownload and install Trino database/sql driver:\n\n```bash\ngo get github.com/trinodb/trino-go-client/trino\n```\n\nMake sure you have Git installed and in your $PATH.\n\n## Usage\n\nThis Trino client is an implementation of Go's `database/sql/driver` interface.\nIn order to use it, you need to import the package and use the\n[`database/sql`](https://golang.org/pkg/database/sql/) API then.\n\nUse `trino` as `driverName` and a valid [DSN](#dsn-data-source-name) as the\n`dataSourceName`.\n\nExample:\n\n```go\nimport \"database/sql\"\nimport _ \"github.com/trinodb/trino-go-client/trino\"\n\ndsn := \"http://user@localhost:8080?catalog=default&schema=test\"\ndb, err := sql.Open(\"trino\", dsn)\n```\n\n### Authentication\n\nBoth HTTP Basic, Kerberos, and JWT authentication are supported.\n\n#### HTTP Basic authentication\n\nIf the DSN contains a password, the client enables HTTP Basic authentication by\nsetting the `Authorization` header in every request to Trino.\n\nHTTP Basic authentication **is only supported on encrypted connections over\nHTTPS**.\n\n#### Kerberos authentication\n\nThis driver supports Kerberos authentication by setting up the Kerberos fields\nin the\n[Config](https://godoc.org/github.com/trinodb/trino-go-client/trino#Config)\nstruct.\n\nPlease refer to the [Coordinator Kerberos\nAuthentication](https://trino.io/docs/current/security/server.html) for\nserver-side configuration.\n\n#### JSON web token authentication\n\nThis driver supports JWT authentication by setting up the `AccessToken` field\nin the\n[Config](https://godoc.org/github.com/trinodb/trino-go-client/trino#Config)\nstruct.\n\nPlease refer to the [Coordinator JWT\nAuthentication](https://trino.io/docs/current/security/jwt.html) for\nserver-side configuration.\n\n#### Authorization header forwarding\nThis driver supports forwarding authorization headers by adding a [NamedArg](https://godoc.org/database/sql#NamedArg) with the name `accessToken` (e.g., `accessToken=<your_access_token>`) and setting the `ForwardAuthorizationHeader` field in the [Config](https://godoc.org/github.com/trinodb/trino-go-client/trino#Config) struct to `true`. \n\nWhen enabled, this configuration will override the `AccessToken` set in the `Config` struct.\n\n\n#### System access control and per-query user information\n\nIt's possible to pass user information to Trino, different from the principal\nused to authenticate to the coordinator. See the [System Access\nControl](https://trino.io/docs/current/develop/system-access-control.html)\ndocumentation for details.\n\nIn order to pass user information in queries to Trino, you have to add a\n[NamedArg](https://godoc.org/database/sql#NamedArg) to the query parameters\nwhere the key is X-Trino-User. This parameter is used by the driver to inform\nTrino about the user executing the query regardless of the authentication\nmethod for the actual connection, and its value is NOT passed to the query.\n\nExample:\n\n```go\ndb.Query(\"SELECT * FROM foobar WHERE id=?\", 1, sql.Named(\"X-Trino-User\", string(\"Alice\")))\n```\n\nThe position of the X-Trino-User NamedArg is irrelevant and does not affect the\nquery in any way.\n\n### DSN (Data Source Name)\n\nThe Data Source Name is a URL with a mandatory username, and optional query\nstring parameters that are supported by this driver, in the following format:\n\n```\nhttp[s]://user[:pass]@host[:port][?parameters]\n```\n\nThe easiest way to build your DSN is by using the\n[Config.FormatDSN](https://godoc.org/github.com/trinodb/trino-go-client/trino#Config.FormatDSN)\nhelper function.\n\nThe driver supports both HTTP and HTTPS. If you use HTTPS it's recommended that\nyou also provide a custom `http.Client` that can validate (or skip) the\nsecurity checks of the server certificate, and/or to configure TLS client\nauthentication.\n\n#### Parameters\n\n*Parameters are case-sensitive*\n\nRefer to the [Trino\nConcepts](https://trino.io/docs/current/overview/concepts.html) documentation\nfor more information.\n\n##### `source`\n\n```\nType:           string\nValid values:   string describing the source of the connection to Trino\nDefault:        empty\n```\n\nThe `source` parameter is optional, but if used, can help Trino admins\ntroubleshoot queries and trace them back to the original client.\n\n##### `catalog`\n\n```\nType:           string\nValid values:   the name of a catalog configured in the Trino server\nDefault:        empty\n```\n\nThe `catalog` parameter defines the Trino catalog where schemas exist to\norganize tables.\n\n##### `schema`\n\n```\nType:           string\nValid values:   the name of an existing schema in the catalog\nDefault:        empty\n```\n\nThe `schema` parameter defines the Trino schema where tables exist. This is\nalso known as namespace in some environments.\n\n##### `session_properties`\n\n```\nType:           string\nValid values:   semicolon-separated list of key:value session properties\nDefault:        empty\n```\n\nThe `session_properties` parameter must contain valid parameters accepted by\nthe Trino server. Run `SHOW SESSION` in Trino to get the current list.\n\n##### `custom_client`\n\n```\nType:           string\nValid values:   the name of a client previously registered to the driver\nDefault:        empty (defaults to http.DefaultClient)\n```\n\nThe `custom_client` parameter allows the use of custom `http.Client` for the\ncommunication with Trino.\n\nRegister your custom client in the driver, then refer to it by name in the DSN,\non the call to `sql.Open`:\n\n```go\nfoobarClient := &http.Client{\n    Transport: &http.Transport{\n        Proxy: http.ProxyFromEnvironment,\n        DialContext: (&net.Dialer{\n            Timeout:   30 * time.Second,\n            KeepAlive: 30 * time.Second,\n            DualStack: true,\n        }).DialContext,\n        MaxIdleConns:          100,\n        IdleConnTimeout:       90 * time.Second,\n        TLSHandshakeTimeout:   10 * time.Second,\n        ExpectContinueTimeout: 1 * time.Second,\n        TLSClientConfig:       &tls.Config{\n        // your config here...\n        },\n    },\n}\ntrino.RegisterCustomClient(\"foobar\", foobarClient)\ndb, err := sql.Open(\"trino\", \"https://user@localhost:8080?custom_client=foobar\")\n```\n\nA custom client can also be used to add OpenTelemetry instrumentation. The\n[otelhttp](https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp)\npackage provides a transport wrapper that creates spans for HTTP requests and\npropagates the trace ID in HTTP headers:\n\n```go\notelClient := &http.Client{\n    Transport: otelhttp.NewTransport(http.DefaultTransport),\n}\ntrino.RegisterCustomClient(\"otel\", otelClient)\ndb, err := sql.Open(\"trino\", \"https://user@localhost:8080?custom_client=otel\")\n```\n\n##### `query_timeout`\n\n```\nType:           time.Duration\nValid values:   duration string\nDefault:        nil\n```\n\nThe `query_timeout` parameter sets a timeout for the query. If the query takes longer than the timeout, it will be cancelled. If it is not set the default context timeout will be used.\n\n##### `explicitPrepare`\n\n```\nType:           string\nValid values:   \"true\", \"false\"\nDefault:        \"true\"\n```\n\nThe `explicitPrepare` parameter controls how queries are sent to the Trino server. When set to `false`, the client uses `EXECUTE IMMEDIATE` which sends the query text in the HTTP request body instead of HTTP headers. This allows sending large query text that would otherwise exceed HTTP header size limits. When set to `true` (default), queries use explicit prepared statements sent via HTTP headers.\n\n##### `clientTags`\n\n```\nType:           string\nValid values:   comma-separated list of tags (e.g. tag1,tag2)\nDefault:        empty\n```\n\nThe `clientTags` parameter is optional and is used to identify Trino resource groups. \nThis helps with query tracking and resource management in Trino clusters.\n\n**DSN parameter example:**\n```\nclientTags=tag1,tag2\n```\n\n**Config struct example:**\n```go\nconfig := &Config{\n    ServerURI:  \"http://foobar@localhost:8080\",\n    ClientTags: []string{\"tag1\", \"tag2\", \"tag3\"},\n}\n\ndsn, err := config.FormatDSN()\n```\n\n**Query parameter example (overrides DSN client tags):**\n```go\nrows, err := db.Query(query, sql.Named(\"X-Trino-Client-Tags\", \"tag1,tag2,tag3\"))\n```\n=======\n\n#### `roles`\n\n```\nType:           string  \nFormat:         roles=catalog1:role1;catalog2=role2  \nValid values:   A semicolon-separated list of catalog-to-role assignments, where each assignment maps a catalog to a role.  \nDefault:        empty\n```\nThe roles parameter defines authorization roles to assume for one or more catalogs during the Trino session.\n\n##### Example\n``` go\nc := &Config{\n\tServerURI:         \"https://foobar@localhost:8090\",\n\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\tRoles:             map[string]string{\"catalog1\": \"role1\", \"catalog2\": \"role2\"},\n}\n\ndsn, err := c.FormatDSN()\n```\n\n**Query parameter example (overrides DSN roles):**\n```go\nrows, err := db.Query(\n    query,\n    sql.Named(\"X-Trino-Role\", map[string]string{\n        \"catalog1\": \"role1\",\n        \"catalog2\": \"role2\",\n    }),\n)\n```\n\n#### Examples\n\n```\nhttp://user@localhost:8080?source=hello&catalog=default&schema=foobar\n```\n\n```\nhttps://user@localhost:8443?session_properties=query_max_run_time:10m;query_priority:2\n```\n\n\n```\nhttp://user@localhost:8080?source=hello&catalog=default&schema=foobar&roles=catalog1:role1;catalog2:role2\n```\n\n## Data types\n\n### Query arguments\n\nWhen passing arguments to queries, the driver supports the following Go data\ntypes:\n* integers\n* `bool`\n* `string`\n* `[]byte`\n* slices\n* `trino.Numeric` - a string representation of a number\n* `time.Time` - passed to Trino as a timestamp with a time zone\n* the result of `trino.Date(year, month, day)` - passed to Trino as a date\n* the result of `trino.Time(hour, minute, second, nanosecond)` - passed to\n  Trino as a time without a time zone\n* the result of `trino.TimeTz(hour, minute, second, nanosecond, location)` -\n  passed to Trino as a time with a time zone\n* the result of `trino.Timestamp(year, month, day, hour, minute, second,\n  nanosecond)` - passed to Trino as a timestamp without a time zone\n* `time.Duration` - passed to Trino as an interval day to second. Because Trino does not support nanosecond precision for intervals, if the nanosecond part of the value is not zero, an error will be returned.\n\nIt's not yet possible to pass:\n* `float32` or `float64`\n* `byte`\n* `json.RawMessage`\n* maps\n\nTo use the unsupported types, pass them as strings and use casts in the query,\nlike so:\n```sql\nSELECT * FROM table WHERE col_double = cast(? AS DOUBLE) OR col_timestamp = CAST(? AS TIMESTAMP)\n```\n\n### Response rows\n\nWhen reading response rows, the driver supports most Trino data types, except:\n* time and timestamps with precision - all time types are returned as\n  `time.Time`. All precisions up to nanoseconds (`TIMESTAMP(9)` or `TIME(9)`)\n  are supported (since this is the maximum precision Golang's `time.Time`\n  supports). If a query returns columns defined with a greater precision,\n  values are trimmed to 9 decimal digits. Use `CAST` to reduce the returned\n  precision, or convert the value to a string that then can be parsed manually.\n* `DECIMAL` - returned as string\n* `IPADDRESS` - returned as string\n* `INTERVAL YEAR TO MONTH` and `INTERVAL DAY TO SECOND` - returned as string\n* `UUID` - returned as string\n\nData types like `HyperLogLog`, `SetDigest`, `QDigest`, and `TDigest` are not\nsupported and cannot be returned from a query.\n\nFor reading nullable columns, use:\n* `trino.NullTime`\n* `trino.NullMap` - which stores a map of `map[string]interface{}`\nor similar structs from the `database/sql` package, like `sql.NullInt64`\n\nTo read query results containing arrays or maps, pass one of the following\nstructs to the `Scan()` function:\n\n* `trino.NullSliceBool`\n* `trino.NullSliceString`\n* `trino.NullSliceInt64`\n* `trino.NullSliceFloat64`\n* `trino.NullSliceTime`\n* `trino.NullSliceMap`\n\nFor two or three dimensional arrays, use `trino.NullSlice2Bool` and\n`trino.NullSlice3Bool` or equivalents for other data types.\n\nTo read `ROW` values, implement the `sql.Scanner` interface in a struct. Its\n`Scan()` function receives a `[]interface{}` slice, with values of the\nfollowing types:\n* `bool`\n* `json.Number` for any numeric Trino types\n* `[]interface{}` for Trino arrays\n* `map[string]interface{}` for Trino maps\n* `string` for other Trino types, as character, date, time, or timestamp.\n\n> [!NOTE]\n> `VARBINARY` columns are returned as base64-encoded strings when used within\n> `ROW`, `MAP`, or `ARRAY` values.\n\n## Spooling Protocol\n\nThe client supports the [Trino spooling protocol](https://trino.io/docs/current/client/client-protocol.html#spooling-protocol), which enables efficient retrieval of large result sets by downloading data in segments, optionally in parallel and out-of-order.\n\nIf the Trino server has the spooling protocol enabled, the client will use it by default with the `json` encoding.\n\nYou can configure other encodings:\n\n- Supported encodings: `json`, `json+lz4`, `json+zstd`\n\n```go\nrows, err := db.Query(query, sql.Named(\"encoding\", \"json+zstd\"))\n```\n\nOr specify a list of supported encodings in order of preference:\n\n```go\nrows, err := db.Query(query, sql.Named(\"encoding\", \"json+zstd, json+lz4, json\"))\n```\n\n### Configuration Options\n\nYou can tune the spooling protocol using the following parameters, passed as `sql.Named` arguments to your query:\n\n- **Spooling Worker Count**  \n  `sql.Named(\"spooling_worker_count\", \"N\")`  \n  Sets the number of parallel workers used to download spooled segments.  \n  **Default:** `5`  \n  **Considerations:**  \n  - Increasing this value can improve throughput for large result sets, especially on high-latency networks.\n  - Higher values increase parallelism but may also increase memory usage.\n\n- **Max Out-of-Order Segments**  \n  `sql.Named(\"max_out_of_order_segments\", \"N\")`  \n  Sets the maximum number of segments that can be downloaded and buffered out-of-order before blocking further downloads.  \n  **Default:** `10`  \n  **Considerations:**  \n  - Higher values increase the potential memory usage, but actual usage depends on download behavior and may be lower in practice.\n  - Higher values reduce the chance that one slow or stalled segment will block the download of additional segments.\n  - Lower values reduce memory usage but may limit parallelism and throughput.\n\n**Note:**  \nIt is **not allowed** to set `spooling_worker_count` higher than `max_out_of_order_segments` — doing so will result in an error.\n\nEach download worker must reserve a slot for the segment it fetches, and a slot is only released when that segment can be processed in order. The total number of slots corresponds to max_out_of_order_segments.\nIf you configure more workers than allowed out-of-order segments, the extra workers would immediately block while waiting for a slot — defeating the purpose of parallelism and potentially wasting resources.\n\n#### Example: Customizing Spooling Parameters\n\n```go\nrows, err := db.Query(\n    query,\n    sql.Named(\"encoding\", \"json+zstd\"),\n    sql.Named(\"spooling_worker_count\", \"8\"),\n    sql.Named(\"max_out_of_order_segments\", \"20\"),\n)\n```\n\n## License\n\nApache License V2.0, as described in the [LICENSE](./LICENSE) file.\n\n## Build\n\nYou can build the client code locally and run tests with the following command:\n\n```\ngo test -v -race -timeout 2m ./...\n```\n\n## Contributing\n\nFor contributing, development, and release guidelines, see\n[CONTRIBUTING.md](./CONTRIBUTING.md).\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/trinodb/trino-go-client\n\ngo 1.24.7\n\nrequire (\n\tgithub.com/ahmetb/dlog v0.0.0-20170105205344-4fb5f8204f26\n\tgithub.com/aws/aws-sdk-go v1.55.8\n\tgithub.com/aws/aws-sdk-go-v2/config v1.31.8\n\tgithub.com/aws/aws-sdk-go-v2/credentials v1.18.12\n\tgithub.com/aws/aws-sdk-go-v2/service/s3 v1.88.1\n\tgithub.com/golang-jwt/jwt/v5 v5.3.0\n\tgithub.com/google/btree v1.1.3\n\tgithub.com/jcmturner/gokrb5/v8 v8.4.4\n\tgithub.com/klauspost/compress v1.18.1\n\tgithub.com/ory/dockertest/v3 v3.12.0\n\tgithub.com/pierrec/lz4 v2.6.1+incompatible\n\tgithub.com/stretchr/testify v1.11.1\n)\n\nrequire (\n\tdario.cat/mergo v1.0.2 // indirect\n\tgithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect\n\tgithub.com/Microsoft/go-winio v0.6.2 // indirect\n\tgithub.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect\n\tgithub.com/ahmetalpbalkan/dlog v0.0.0-20170105205344-4fb5f8204f26 // indirect\n\tgithub.com/aws/aws-sdk-go-v2 v1.39.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sso v1.29.3 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.4 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sts v1.38.4 // indirect\n\tgithub.com/aws/smithy-go v1.23.0 // indirect\n\tgithub.com/cenkalti/backoff/v4 v4.3.0 // indirect\n\tgithub.com/containerd/continuity v0.4.5 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/docker/cli v28.4.0+incompatible // indirect\n\tgithub.com/docker/docker v28.4.0+incompatible // indirect\n\tgithub.com/docker/go-connections v0.6.0 // indirect\n\tgithub.com/docker/go-units v0.5.0 // indirect\n\tgithub.com/frankban/quicktest v1.14.6 // indirect\n\tgithub.com/go-viper/mapstructure/v2 v2.4.0 // indirect\n\tgithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect\n\tgithub.com/hashicorp/go-uuid v1.0.3 // indirect\n\tgithub.com/jcmturner/aescts/v2 v2.0.0 // indirect\n\tgithub.com/jcmturner/dnsutils/v2 v2.0.0 // indirect\n\tgithub.com/jcmturner/gofork v1.7.6 // indirect\n\tgithub.com/jcmturner/goidentity/v6 v6.0.1 // indirect\n\tgithub.com/jcmturner/rpc/v2 v2.0.3 // indirect\n\tgithub.com/moby/docker-image-spec v1.3.1 // indirect\n\tgithub.com/moby/sys/user v0.4.0 // indirect\n\tgithub.com/moby/term v0.5.2 // indirect\n\tgithub.com/opencontainers/go-digest v1.0.0 // indirect\n\tgithub.com/opencontainers/image-spec v1.1.1 // indirect\n\tgithub.com/opencontainers/runc v1.3.1 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/sirupsen/logrus v1.9.3 // indirect\n\tgithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect\n\tgithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect\n\tgithub.com/xeipuuv/gojsonschema v1.2.0 // indirect\n\tgolang.org/x/crypto v0.45.0 // indirect\n\tgolang.org/x/net v0.47.0 // indirect\n\tgolang.org/x/sys v0.38.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=\ndario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=\nfilippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=\ngithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=\ngithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=\ngithub.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=\ngithub.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=\ngithub.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=\ngithub.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=\ngithub.com/ahmetalpbalkan/dlog v0.0.0-20170105205344-4fb5f8204f26 h1:pzStYMLAXM7CNQjS/Wn+zK9MUxDhSUNfVvnHsyQyjs0=\ngithub.com/ahmetalpbalkan/dlog v0.0.0-20170105205344-4fb5f8204f26/go.mod h1:ilK+u7u1HoqaDk0mjhh27QJB7PyWMreGffEvOCoEKiY=\ngithub.com/ahmetb/dlog v0.0.0-20170105205344-4fb5f8204f26 h1:3YVZUqkoev4mL+aCwVOSWV4M7pN+NURHL38Z2zq5JKA=\ngithub.com/ahmetb/dlog v0.0.0-20170105205344-4fb5f8204f26/go.mod h1:ymXt5bw5uSNu4jveerFxE0vNYxF8ncqbptntMaFMg3k=\ngithub.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=\ngithub.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=\ngithub.com/aws/aws-sdk-go-v2 v1.39.0 h1:xm5WV/2L4emMRmMjHFykqiA4M/ra0DJVSWUkDyBjbg4=\ngithub.com/aws/aws-sdk-go-v2 v1.39.0/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00=\ngithub.com/aws/aws-sdk-go-v2/config v1.31.8 h1:kQjtOLlTU4m4A64TsRcqwNChhGCwaPBt+zCQt/oWsHU=\ngithub.com/aws/aws-sdk-go-v2/config v1.31.8/go.mod h1:QPpc7IgljrKwH0+E6/KolCgr4WPLerURiU592AYzfSY=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.18.12 h1:zmc9e1q90wMn8wQbjryy8IwA6Q4XlaL9Bx2zIqdNNbk=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.18.12/go.mod h1:3VzdRDR5u3sSJRI4kYcOSIBbeYsgtVk7dG5R/U6qLWY=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 h1:Is2tPmieqGS2edBnmOJIbdvOA6Op+rRpaYR60iBAwXM=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7/go.mod h1:F1i5V5421EGci570yABvpIXgRIBPb5JM+lSkHF6Dq5w=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 h1:UCxq0X9O3xrlENdKf1r9eRJoKz/b0AfGkpp3a7FPlhg=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7/go.mod h1:rHRoJUNUASj5Z/0eqI4w32vKvC7atoWR0jC+IkmVH8k=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 h1:Y6DTZUn7ZUC4th9FMBbo8LVE+1fyq3ofw+tRwkUd3PY=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7/go.mod h1:x3XE6vMnU9QvHN/Wrx2s44kwzV2o2g5x/siw4ZUJ9g8=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7 h1:BszAktdUo2xlzmYHjWMq70DqJ7cROM8iBd3f6hrpuMQ=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7/go.mod h1:XJ1yHki/P7ZPuG4fd3f0Pg/dSGA2cTQBCLw82MH2H48=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7 h1:zmZ8qvtE9chfhBPuKB2aQFxW5F/rpwXUgmcVCgQzqRw=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7/go.mod h1:vVYfbpd2l+pKqlSIDIOgouxNsGu5il9uDp0ooWb0jys=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 h1:mLgc5QIgOy26qyh5bvW+nDoAppxgn3J2WV3m9ewq7+8=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7/go.mod h1:wXb/eQnqt8mDQIQTTmcw58B5mYGxzLGZGK8PWNFZ0BA=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7 h1:u3VbDKUCWarWiU+aIUK4gjTr/wQFXV17y3hgNno9fcA=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7/go.mod h1:/OuMQwhSyRapYxq6ZNpPer8juGNrB4P5Oz8bZ2cgjQE=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.88.1 h1:+RpGuaQ72qnU83qBKVwxkznewEdAGhIWo/PQCmkhhog=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.88.1/go.mod h1:xajPTguLoeQMAOE44AAP2RQoUhF8ey1g5IFHARv71po=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.29.3 h1:7PKX3VYsZ8LUWceVRuv0+PU+E7OtQb1lgmi5vmUE9CM=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.29.3/go.mod h1:Ql6jE9kyyWI5JHn+61UT/Y5Z0oyVJGmgmJbZD5g4unY=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.4 h1:e0XBRn3AptQotkyBFrHAxFB8mDhAIOfsG+7KyJ0dg98=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.4/go.mod h1:XclEty74bsGBCr1s0VSaA11hQ4ZidK4viWK7rRfO88I=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.38.4 h1:PR00NXRYgY4FWHqOGx3fC3lhVKjsp1GdloDv2ynMSd8=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.38.4/go.mod h1:Z+Gd23v97pX9zK97+tX4ppAgqCt3Z2dIXB02CtBncK8=\ngithub.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=\ngithub.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=\ngithub.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=\ngithub.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\ngithub.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=\ngithub.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=\ngithub.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=\ngithub.com/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/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=\ngithub.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=\ngithub.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=\ngithub.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=\ngithub.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=\ngithub.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=\ngithub.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=\ngithub.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=\ngithub.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=\ngithub.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=\ngithub.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=\ngithub.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=\ngithub.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=\ngithub.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=\ngithub.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=\ngithub.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=\ngithub.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=\ngithub.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=\ngithub.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=\ngithub.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=\ngithub.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=\ngithub.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=\ngithub.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=\ngithub.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=\ngithub.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=\ngithub.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=\ngithub.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=\ngithub.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=\ngithub.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=\ngithub.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=\ngithub.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\ngithub.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=\ngithub.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=\ngithub.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=\ngithub.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=\ngithub.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=\ngithub.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=\ngithub.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=\ngithub.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=\ngithub.com/opencontainers/runc v1.3.1 h1:c/yY0oh2wK7tzDuD56REnSxyU8ubh8hoAIOLGLrm4SM=\ngithub.com/opencontainers/runc v1.3.1/go.mod h1:9wbWt42gV+KRxKRVVugNP6D5+PQciRbenB4fLVsqGPs=\ngithub.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw=\ngithub.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE=\ngithub.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=\ngithub.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=\ngithub.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=\ngithub.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.7.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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=\ngithub.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=\ngithub.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=\ngolang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=\ngolang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=\ngolang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210616094352-59db8d763f22/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=\ngotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=\n"
  },
  {
    "path": "trino/etc/catalog/hive.properties",
    "content": "connector.name=hive\nhive.metastore=file\nhive.metastore.catalog.dir=/tmp/metastore\nhive.security=sql-standard\nfs.hadoop.enabled=true"
  },
  {
    "path": "trino/etc/catalog/memory.properties",
    "content": "connector.name=memory\n"
  },
  {
    "path": "trino/etc/catalog/tpch.properties",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\nconnector.name=tpch\n"
  },
  {
    "path": "trino/etc/config-pre-466version.properties",
    "content": "coordinator=true\nnode-scheduler.include-coordinator=true\nhttp-server.http.port=8080\ndiscovery-server.enabled=true\ndiscovery.uri=http://localhost:8080\n\nhttp-server.authentication.type=PASSWORD,JWT\nhttp-server.authentication.jwt.key-file=/etc/trino/secrets/public_key.pem\nhttp-server.https.enabled=true\nhttp-server.https.port=8443\nhttp-server.authentication.allow-insecure-over-http=true\nhttp-server.https.keystore.path=/etc/trino/secrets/certificate_with_key.pem\ninternal-communication.shared-secret=gotrino\n\nquery.max-length=5000043\n"
  },
  {
    "path": "trino/etc/config-pre-477version.properties",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\ncoordinator=true\nnode-scheduler.include-coordinator=true\nhttp-server.http.port=8080\ndiscovery-server.enabled=true\ndiscovery.uri=http://localhost:8080\n\nhttp-server.authentication.type=PASSWORD,JWT\nhttp-server.authentication.jwt.key-file=/etc/trino/secrets/public_key.pem\nhttp-server.https.enabled=true\nhttp-server.https.port=8443\nhttp-server.authentication.allow-insecure-over-http=true\nhttp-server.https.keystore.path=/etc/trino/secrets/certificate_with_key.pem\ninternal-communication.shared-secret=gotrino\n\nquery.max-length=5000043\n\n## spooling protocol settings\nprotocol.spooling.enabled=true\nprotocol.spooling.shared-secret-key=jxTKysfCBuMZtFqUf8UJDQ1w9ez8rynEJsJqgJf66u0=\nprotocol.spooling.retrieval-mode=coordinator_proxy\n# Max number of rows to inline per worker\n# If the number of rows exceeds this threshold, spooled segments will be returned.\n# If the number of rows is within this threshold and the max size is below the max-size threshold,\n# inline segments will be returne\nprotocol.spooling.inlining.max-rows=1000\n\n# Max size of rows to inline per worker\n# If the total size of the rows exceeds this threshold, spooled segments will be returned.\n# If the total size of the rows is within this threshold and the row count is below the max-rows threshold,\n# inline segments will be returned.\nprotocol.spooling.inlining.max-size=128kB\n"
  },
  {
    "path": "trino/etc/config.properties",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\ncoordinator=true\nnode-scheduler.include-coordinator=true\nhttp-server.http.port=8080\n\nhttp-server.authentication.type=PASSWORD,JWT\nhttp-server.authentication.jwt.key-file=/etc/trino/secrets/public_key.pem\nhttp-server.https.enabled=true\nhttp-server.https.port=8443\nhttp-server.authentication.allow-insecure-over-http=true\nhttp-server.https.keystore.path=/etc/trino/secrets/certificate_with_key.pem\ninternal-communication.shared-secret=gotrino\n\nquery.max-length=5000043\n\n## spooling protocol settings\nprotocol.spooling.enabled=true\nprotocol.spooling.shared-secret-key=jxTKysfCBuMZtFqUf8UJDQ1w9ez8rynEJsJqgJf66u0=\nprotocol.spooling.retrieval-mode=coordinator_proxy\n# Max number of rows to inline per worker\n# If the number of rows exceeds this threshold, spooled segments will be returned.\n# If the number of rows is within this threshold and the max size is below the max-size threshold,\n# inline segments will be returne\nprotocol.spooling.inlining.max-rows=1000\n\n# Max size of rows to inline per worker\n# If the total size of the rows exceeds this threshold, spooled segments will be returned.\n# If the total size of the rows is within this threshold and the row count is below the max-rows threshold,\n# inline segments will be returned.\nprotocol.spooling.inlining.max-size=128kB\n"
  },
  {
    "path": "trino/etc/jvm.config",
    "content": "-Xmx4G\n-XX:+UseG1GC\n-XX:G1HeapRegionSize=32M\n-XX:+UseGCOverheadLimit\n-XX:+ExplicitGCInvokesConcurrent\n-XX:+ExitOnOutOfMemoryError\n-Djdk.attach.allowAttachSelf=true\n-Djdk.nio.maxCachedBufferSize=2000000\n"
  },
  {
    "path": "trino/etc/node.properties",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\nnode.environment=test\nnode.id=test\nnode.data-dir=/data/trino\n"
  },
  {
    "path": "trino/etc/password-authenticator.properties",
    "content": "password-authenticator.name=file\nfile.password-file=/etc/trino/secrets/password.db\n"
  },
  {
    "path": "trino/etc/secrets/.gitignore",
    "content": "*.pem\n"
  },
  {
    "path": "trino/etc/spooling-manager.properties",
    "content": "spooling-manager.name=filesystem\nfs.s3.enabled=true\nfs.location=s3://spooling/\ns3.endpoint=http://localstack:4566/\ns3.region=us-east-1\ns3.aws-access-key=test\ns3.aws-secret-key=test\ns3.path-style-access=true\n\n"
  },
  {
    "path": "trino/integration_test.go",
    "content": "// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage trino\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"database/sql\"\n\t\"database/sql/driver\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"math\"\n\t\"math/big\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/ahmetb/dlog\"\n\t\"github.com/aws/aws-sdk-go-v2/config\"\n\t\"github.com/aws/aws-sdk-go-v2/credentials\"\n\t\"github.com/aws/aws-sdk-go-v2/service/s3\"\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/golang-jwt/jwt/v5\"\n\tdt \"github.com/ory/dockertest/v3\"\n\tdocker \"github.com/ory/dockertest/v3/docker\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst (\n\tDockerLocalStackName = \"localstack\"\n\tbucketName           = \"spooling\"\n\tDockerTrinoName      = \"trino-go-client-tests\"\n\tMAXRetries           = 10\n\tTrinoNetwork         = \"trino-network\"\n)\n\nvar (\n\tpool                      *dt.Pool\n\ttrinoResource             *dt.Resource\n\tlocalStackResource        *dt.Resource\n\tspoolingProtocolSupported bool\n\n\ttrinoImageTagFlag = flag.String(\n\t\t\"trino_image_tag\",\n\t\tos.Getenv(\"TRINO_IMAGE_TAG\"),\n\t\t\"Docker image tag used for the Trino server container\",\n\t)\n\tintegrationServerFlag = flag.String(\n\t\t\"trino_server_dsn\",\n\t\tos.Getenv(\"TRINO_SERVER_DSN\"),\n\t\t\"dsn of the Trino server used for integration tests instead of starting a Docker container\",\n\t)\n\tintegrationServerQueryTimeout = flag.Duration(\n\t\t\"trino_query_timeout\",\n\t\t5*time.Second,\n\t\t\"max duration for Trino queries to run before giving up\",\n\t)\n\tnoCleanup = flag.Bool(\n\t\t\"no_cleanup\",\n\t\tfalse,\n\t\t\"do not delete containers on exit\",\n\t)\n\ttlsServer = \"\"\n)\n\nfunc TestMain(m *testing.M) {\n\tflag.Parse()\n\tDefaultQueryTimeout = *integrationServerQueryTimeout\n\tDefaultCancelQueryTimeout = *integrationServerQueryTimeout\n\tif *trinoImageTagFlag == \"\" {\n\t\t*trinoImageTagFlag = \"latest\"\n\t}\n\n\tif *trinoImageTagFlag == \"latest\" {\n\t\tspoolingProtocolSupported = true\n\t} else {\n\t\tversion, err := strconv.Atoi(*trinoImageTagFlag)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Invalid trino_image_tag: %s\", *trinoImageTagFlag)\n\t\t}\n\t\tspoolingProtocolSupported = version >= 466\n\t}\n\n\tvar err error\n\tif *integrationServerFlag == \"\" && !testing.Short() {\n\t\tpool, err = dt.NewPool(\"\")\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Could not connect to docker: %s\", err)\n\t\t}\n\t\tpool.MaxWait = 1 * time.Minute\n\n\t\tnetworkID := getOrCreateNetwork(pool)\n\n\t\twd, err := os.Getwd()\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Failed to get working directory: %s\", err)\n\t\t}\n\n\t\tvar ok bool\n\t\tif spoolingProtocolSupported {\n\t\t\tlocalStackResource = getOrCreateLocalStack(pool, networkID)\n\t\t}\n\n\t\ttrinoResource, ok = pool.ContainerByName(DockerTrinoName)\n\n\t\tif !ok {\n\t\t\terr = generateCerts(wd + \"/etc/secrets\")\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalf(\"Could not generate TLS certificates: %s\", err)\n\t\t\t}\n\n\t\t\tmounts := []string{\n\t\t\t\twd + \"/etc/secrets:/etc/trino/secrets\",\n\t\t\t\twd + \"/etc/jvm.config:/etc/trino/jvm.config\",\n\t\t\t\twd + \"/etc/node.properties:/etc/trino/node.properties\",\n\t\t\t\twd + \"/etc/password-authenticator.properties:/etc/trino/password-authenticator.properties\",\n\t\t\t\twd + \"/etc/catalog/memory.properties:/etc/trino/catalog/memory.properties\",\n\t\t\t\twd + \"/etc/catalog/tpch.properties:/etc/trino/catalog/tpch.properties\",\n\t\t\t}\n\t\t\tversion, err := strconv.Atoi(*trinoImageTagFlag)\n\t\t\tif (err != nil && *trinoImageTagFlag == \"latest\") || (err == nil && version >= 458) {\n\t\t\t\tmounts = append(mounts, wd+\"/etc/catalog/hive.properties:/etc/trino/catalog/hive.properties\")\n\t\t\t}\n\n\t\t\tif spoolingProtocolSupported {\n\t\t\t\tversion, err := strconv.Atoi(*trinoImageTagFlag)\n\t\t\t\tif (err != nil && *trinoImageTagFlag != \"latest\") || (err == nil && version < 477) {\n\t\t\t\t\tmounts = append(mounts, wd+\"/etc/config-pre-477version.properties:/etc/trino/config.properties\")\n\t\t\t\t} else {\n\t\t\t\t\tmounts = append(mounts, wd+\"/etc/config.properties:/etc/trino/config.properties\")\n\t\t\t\t}\n\t\t\t\tmounts = append(mounts, wd+\"/etc/spooling-manager.properties:/etc/trino/spooling-manager.properties\")\n\t\t\t} else {\n\t\t\t\tmounts = append(mounts, wd+\"/etc/config-pre-466version.properties:/etc/trino/config.properties\")\n\t\t\t}\n\t\t\ttrinoResource, err = pool.RunWithOptions(&dt.RunOptions{\n\t\t\t\tName:       DockerTrinoName,\n\t\t\t\tRepository: \"trinodb/trino\",\n\t\t\t\tTag:        *trinoImageTagFlag,\n\t\t\t\tMounts:     mounts,\n\t\t\t\tExposedPorts: []string{\n\t\t\t\t\t\"8080/tcp\",\n\t\t\t\t\t\"8443/tcp\",\n\t\t\t\t},\n\t\t\t\tNetworkID: networkID,\n\t\t\t}, func(hc *docker.HostConfig) {\n\t\t\t\thc.Ulimits = []docker.ULimit{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"nofile\",\n\t\t\t\t\t\tHard: 4096,\n\t\t\t\t\t\tSoft: 4096,\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\tlog.Fatalf(\"Could not start resource: %s\", err)\n\t\t\t}\n\t\t} else if !trinoResource.Container.State.Running {\n\t\t\tpool.Client.StartContainer(trinoResource.Container.ID, nil)\n\t\t}\n\n\t\twaitForContainerHealth(trinoResource.Container.ID, \"trino\")\n\n\t\terr = grantAdminRoleToTestUser()\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Warning: Failed to grant admin role to test user: %s\", err)\n\t\t}\n\n\t\t*integrationServerFlag = \"http://test@localhost:\" + trinoResource.GetPort(\"8080/tcp\")\n\t\ttlsServer = \"https://admin:admin@localhost:\" + trinoResource.GetPort(\"8443/tcp\")\n\n\t\thttp.DefaultTransport.(*http.Transport).TLSClientConfig, err = getTLSConfig(wd + \"/etc/secrets\")\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Failed to set the default TLS config: %s\", err)\n\t\t}\n\t}\n\n\tcode := m.Run()\n\n\tif !*noCleanup && pool != nil {\n\t\tif trinoResource != nil {\n\t\t\tif err := pool.Purge(trinoResource); err != nil {\n\t\t\t\tlog.Fatalf(\"Could not purge resource: %s\", err)\n\t\t\t}\n\t\t}\n\n\t\tif localStackResource != nil {\n\t\t\tif err := pool.Purge(localStackResource); err != nil {\n\t\t\t\tlog.Fatalf(\"Could not purge LocalStack resource: %s\", err)\n\t\t\t}\n\t\t}\n\n\t\tnetworkExists, networkID, err := networkExists(pool, TrinoNetwork)\n\t\tif err == nil && networkExists {\n\t\t\tif err := pool.Client.RemoveNetwork(networkID); err != nil {\n\t\t\t\tlog.Fatalf(\"Could not remove Docker network: %s\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tos.Exit(code)\n}\n\nfunc grantAdminRoleToTestUser() error {\n\tgrantSQL := \"SET ROLE admin IN hive; GRANT admin TO USER test IN hive;\"\n\n\texecCmd := []string{\n\t\t\"trino\",\n\t\t\"--user\", \"admin\",\n\t\t\"--execute\", grantSQL,\n\t}\n\texec, err := pool.Client.CreateExec(docker.CreateExecOptions{\n\t\tContainer: trinoResource.Container.ID,\n\t\tCmd:       execCmd,\n\t})\n\tif err != nil {\n\t\tlog.Printf(\"Warning: Failed to create exec for GRANT: %s\", err)\n\t} else {\n\t\tvar stdout, stderr bytes.Buffer\n\t\terr = pool.Client.StartExec(exec.ID, docker.StartExecOptions{\n\t\t\tDetach:       false,\n\t\t\tOutputStream: &stdout,\n\t\t\tErrorStream:  &stderr,\n\t\t})\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Warning: Failed to execute GRANT: %s\", err)\n\t\t}\n\t}\n\n\treturn err\n}\n\nfunc getOrCreateLocalStack(pool *dt.Pool, networkID string) *dt.Resource {\n\tresource, ok := pool.ContainerByName(DockerLocalStackName)\n\tif ok {\n\t\treturn resource\n\t}\n\n\tnewResource, err := setupLocalStack(pool, networkID)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to start LocalStack: %s\", err)\n\t}\n\n\treturn newResource\n}\n\nfunc getOrCreateNetwork(pool *dt.Pool) string {\n\tnetworkExists, networkID, err := networkExists(pool, TrinoNetwork)\n\tif err != nil {\n\t\tlog.Fatalf(\"Could not check if Docker network exists: %s\", err)\n\t}\n\n\tif networkExists {\n\t\treturn networkID\n\t}\n\n\tnetwork, err := pool.Client.CreateNetwork(docker.CreateNetworkOptions{\n\t\tName: TrinoNetwork,\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"Could not create Docker network: %s\", err)\n\t}\n\n\treturn network.ID\n}\n\nfunc networkExists(pool *dt.Pool, networkName string) (bool, string, error) {\n\tnetworks, err := pool.Client.ListNetworks()\n\tif err != nil {\n\t\treturn false, \"\", fmt.Errorf(\"could not list Docker networks: %w\", err)\n\t}\n\tfor _, network := range networks {\n\t\tif network.Name == networkName {\n\t\t\treturn true, network.ID, nil\n\t\t}\n\t}\n\treturn false, \"\", nil\n}\n\nfunc setupLocalStack(pool *dt.Pool, networkID string) (*dt.Resource, error) {\n\tlocalstackResource, err := pool.RunWithOptions(&dt.RunOptions{\n\t\tName:       DockerLocalStackName,\n\t\tRepository: \"localstack/localstack\",\n\t\tTag:        \"latest\",\n\t\tEnv: []string{\n\t\t\t\"SERVICES=s3\",\n\t\t\t\"region_name=us-east-1\",\n\t\t\t\"AWS_ACCESS_KEY_ID=test\",\n\t\t\t\"AWS_SECRET_ACCESS_KEY=test\",\n\t\t},\n\n\t\tPortBindings: map[docker.Port][]docker.PortBinding{\n\t\t\t\"4566/tcp\": {{HostIP: \"0.0.0.0\", HostPort: \"4566\"}},\n\t\t\t\"4571/tcp\": {{HostIP: \"0.0.0.0\", HostPort: \"4571\"}},\n\t\t},\n\n\t\tNetworkID: networkID,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not start LocalStack: %w\", err)\n\t}\n\n\tlocalstackPort := localstackResource.GetPort(\"4566/tcp\")\n\ts3Endpoint := \"http://localhost:\" + localstackPort\n\n\tlog.Println(\"LocalStack started at:\", s3Endpoint)\n\n\twaitForContainerHealth(localstackResource.Container.ID, \"localstack\")\n\n\tfor retry := 0; retry < MAXRetries; retry++ {\n\t\terr := createS3Bucket(s3Endpoint, \"test\", \"test\", bucketName)\n\t\tif err == nil {\n\t\t\tlog.Println(\"S3 bucket created successfully\")\n\t\t\treturn localstackResource, nil\n\t\t}\n\t\tlog.Printf(\"Failed to create S3 bucket, retrying... (%d/%d)\\n\", retry+1, MAXRetries)\n\t\ttime.Sleep(2 * time.Second)\n\t}\n\n\treturn nil, fmt.Errorf(\"failed to create S3 bucket after multiple attempts: %w\", err)\n}\n\nfunc createS3Bucket(endpoint, accessKey, secretKey, bucketName string) error {\n\tcfg, err := config.LoadDefaultConfig(context.TODO(),\n\t\tconfig.WithRegion(\"us-east-1\"),\n\t\tconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, \"\")),\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to load AWS config: %w\", err)\n\t}\n\n\ts3Client := s3.New(s3.Options{\n\t\tCredentials:  cfg.Credentials,\n\t\tRegion:       \"us-east-1\",\n\t\tBaseEndpoint: &endpoint,\n\t\tUsePathStyle: *aws.Bool(true),\n\t})\n\n\tcreateBucketInput := &s3.CreateBucketInput{\n\t\tBucket: aws.String(bucketName),\n\t}\n\n\t_, err = s3Client.CreateBucket(context.TODO(), createBucketInput)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create S3 bucket: %w\", err)\n\t}\n\n\tlog.Printf(\"Bucket %s created successfully!\", bucketName)\n\treturn nil\n}\n\nfunc waitForContainerHealth(containerID, containerName string) {\n\tif err := pool.Retry(func() error {\n\t\tc, err := pool.Client.InspectContainer(containerID)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Failed to inspect container %s: %s\", containerID, err)\n\t\t}\n\t\tif !c.State.Running {\n\t\t\tlog.Fatalf(\"Container %s is not running: %s\\nContainer logs:\\n%s\", containerID, c.State.String(), getLogs(trinoResource.Container.ID))\n\t\t}\n\t\tlog.Printf(\"Waiting for %s container: %s\\n\", containerName, c.State.String())\n\t\tif c.State.Health.Status != \"healthy\" {\n\t\t\treturn errors.New(\"Not ready\")\n\t\t}\n\t\treturn nil\n\t}); err != nil {\n\t\tlog.Fatalf(\"Timed out waiting for container %s to get ready: %s\\nContainer logs:\\n%s\", containerName, err, getLogs(containerID))\n\t}\n}\n\nfunc generateCerts(dir string) error {\n\tpriv, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to generate private key: %w\", err)\n\t}\n\n\tserialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)\n\tserialNumber, err := rand.Int(rand.Reader, serialNumberLimit)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to generate serial number: %w\", err)\n\t}\n\n\ttemplate := x509.Certificate{\n\t\tSerialNumber: serialNumber,\n\t\tSubject: pkix.Name{\n\t\t\tOrganization: []string{\"Trino Software Foundation\"},\n\t\t},\n\t\tDNSNames:              []string{\"localhost\"},\n\t\tNotBefore:             time.Now(),\n\t\tNotAfter:              time.Now().Add(1 * time.Hour),\n\t\tKeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t\tBasicConstraintsValid: true,\n\t}\n\n\tprivBytes, err := x509.MarshalPKCS8PrivateKey(priv)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to marshal private key: %w\", err)\n\t}\n\tprivBlock := &pem.Block{Type: \"PRIVATE KEY\", Bytes: privBytes}\n\terr = writePEM(dir+\"/private_key.pem\", privBlock)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpubBytes, err := x509.MarshalPKIXPublicKey(&priv.PublicKey)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to marshal public key: %w\", err)\n\t}\n\tpubBlock := &pem.Block{Type: \"PUBLIC KEY\", Bytes: pubBytes}\n\terr = writePEM(dir+\"/public_key.pem\", pubBlock)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcertBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create certificate: %w\", err)\n\t}\n\tcertBlock := &pem.Block{Type: \"CERTIFICATE\", Bytes: certBytes}\n\terr = writePEM(dir+\"/certificate.pem\", certBlock)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = writePEM(dir+\"/certificate_with_key.pem\", certBlock, privBlock, pubBlock)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc writePEM(filename string, blocks ...*pem.Block) error {\n\t// all files are world-readable, so they can be read inside the Trino container\n\tout, err := os.Create(filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open %s for writing: %w\", filename, err)\n\t}\n\tfor _, block := range blocks {\n\t\tif err := pem.Encode(out, block); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write %s data to %s: %w\", block.Type, filename, err)\n\t\t}\n\t}\n\tif err := out.Close(); err != nil {\n\t\treturn fmt.Errorf(\"error closing %s: %w\", filename, err)\n\t}\n\treturn nil\n}\n\nfunc getTLSConfig(dir string) (*tls.Config, error) {\n\tcertPool, err := x509.SystemCertPool()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read the system cert pool: %s\", err)\n\t}\n\tcaCertPEM, err := os.ReadFile(dir + \"/certificate.pem\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read the certificate: %s\", err)\n\t}\n\tok := certPool.AppendCertsFromPEM(caCertPEM)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"failed to parse the certificate: %s\", err)\n\t}\n\treturn &tls.Config{\n\t\tRootCAs: certPool,\n\t}, nil\n}\n\nfunc getLogs(id string) []byte {\n\tvar buf bytes.Buffer\n\tpool.Client.Logs(docker.LogsOptions{\n\t\tContainer:    id,\n\t\tOutputStream: &buf,\n\t\tErrorStream:  &buf,\n\t\tStdout:       true,\n\t\tStderr:       true,\n\t\tRawTerminal:  true,\n\t})\n\tlogs, _ := io.ReadAll(dlog.NewReader(&buf))\n\treturn logs\n}\n\n// integrationOpen opens a connection to the integration test server.\nfunc integrationOpen(t *testing.T, dsn ...string) *sql.DB {\n\tif testing.Short() {\n\t\tt.Skip(\"Skipping test in short mode.\")\n\t}\n\ttarget := *integrationServerFlag\n\tif len(dsn) > 0 {\n\t\ttarget = dsn[0]\n\t}\n\tdb, err := sql.Open(\"trino\", target)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn db\n}\n\n// integration tests based on python tests:\n// https://github.com/trinodb/trino-python-client/tree/master/integration_tests\n\ntype nodesRow struct {\n\tNodeID      string\n\tHTTPURI     string\n\tNodeVersion string\n\tCoordinator bool\n\tState       string\n}\n\nfunc TestIntegrationSelectQueryIterator(t *testing.T) {\n\tdb := integrationOpen(t)\n\tdefer db.Close()\n\trows, err := db.Query(\"SELECT * FROM system.runtime.nodes\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer rows.Close()\n\tcount := 0\n\tfor rows.Next() {\n\t\tcount++\n\t\tvar col nodesRow\n\t\terr = rows.Scan(\n\t\t\t&col.NodeID,\n\t\t\t&col.HTTPURI,\n\t\t\t&col.NodeVersion,\n\t\t\t&col.Coordinator,\n\t\t\t&col.State,\n\t\t)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif col.NodeID != \"test\" {\n\t\t\tt.Errorf(\"Expected node_id == test but got %s\", col.NodeID)\n\t\t}\n\t}\n\tif err = rows.Err(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif count < 1 {\n\t\tt.Error(\"no rows returned\")\n\t}\n}\n\nfunc TestIntegrationSelectQueryNoResult(t *testing.T) {\n\tdb := integrationOpen(t)\n\tdefer db.Close()\n\trow := db.QueryRow(\"SELECT * FROM system.runtime.nodes where false\")\n\tvar col nodesRow\n\terr := row.Scan(\n\t\t&col.NodeID,\n\t\t&col.HTTPURI,\n\t\t&col.NodeVersion,\n\t\t&col.Coordinator,\n\t\t&col.State,\n\t)\n\tif err == nil {\n\t\tt.Fatalf(\"unexpected query returning data: %+v\", col)\n\t}\n}\n\nfunc TestIntegrationSelectFailedQuery(t *testing.T) {\n\tdb := integrationOpen(t)\n\tdefer db.Close()\n\trows, err := db.Query(\"SELECT * FROM catalog.schema.do_not_exist\")\n\tif err == nil {\n\t\trows.Close()\n\t\tt.Fatal(\"query to invalid catalog succeeded\")\n\t}\n\tqueryFailed, ok := err.(*ErrQueryFailed)\n\tif !ok {\n\t\tt.Fatal(\"unexpected error:\", err)\n\t}\n\ttrinoErr, ok := errors.Unwrap(queryFailed).(*ErrTrino)\n\tif !ok {\n\t\tt.Fatal(\"unexpected error:\", trinoErr)\n\t}\n\texpected := ErrTrino{\n\t\tMessage:   \"line 1:15: Catalog 'catalog'\",\n\t\tSqlState:  \"\",\n\t\tErrorCode: 44,\n\t\tErrorName: \"CATALOG_NOT_FOUND\",\n\t\tErrorType: \"USER_ERROR\",\n\t\tErrorLocation: ErrorLocation{\n\t\t\tLineNumber:   1,\n\t\t\tColumnNumber: 15,\n\t\t},\n\t\tFailureInfo: FailureInfo{\n\t\t\tType:    \"io.trino.spi.TrinoException\",\n\t\t\tMessage: \"line 1:15: Catalog 'catalog'\",\n\t\t},\n\t}\n\tif !strings.HasPrefix(trinoErr.Message, expected.Message) {\n\t\tt.Fatalf(\"expected ErrTrino.Message to start with `%s`, got: %s\", expected.Message, trinoErr.Message)\n\t}\n\tif trinoErr.SqlState != expected.SqlState {\n\t\tt.Fatalf(\"expected ErrTrino.SqlState to be `%s`, got: %s\", expected.SqlState, trinoErr.SqlState)\n\t}\n\tif trinoErr.ErrorCode != expected.ErrorCode {\n\t\tt.Fatalf(\"expected ErrTrino.ErrorCode to be `%d`, got: %d\", expected.ErrorCode, trinoErr.ErrorCode)\n\t}\n\tif trinoErr.ErrorName != expected.ErrorName {\n\t\tt.Fatalf(\"expected ErrTrino.ErrorName to be `%s`, got: %s\", expected.ErrorName, trinoErr.ErrorName)\n\t}\n\tif trinoErr.ErrorType != expected.ErrorType {\n\t\tt.Fatalf(\"expected ErrTrino.ErrorType to be `%s`, got: %s\", expected.ErrorType, trinoErr.ErrorType)\n\t}\n\tif trinoErr.ErrorLocation.LineNumber != expected.ErrorLocation.LineNumber {\n\t\tt.Fatalf(\"expected ErrTrino.ErrorLocation.LineNumber to be `%d`, got: %d\", expected.ErrorLocation.LineNumber, trinoErr.ErrorLocation.LineNumber)\n\t}\n\tif trinoErr.ErrorLocation.ColumnNumber != expected.ErrorLocation.ColumnNumber {\n\t\tt.Fatalf(\"expected ErrTrino.ErrorLocation.ColumnNumber to be `%d`, got: %d\", expected.ErrorLocation.ColumnNumber, trinoErr.ErrorLocation.ColumnNumber)\n\t}\n\tif trinoErr.FailureInfo.Type != expected.FailureInfo.Type {\n\t\tt.Fatalf(\"expected ErrTrino.FailureInfo.Type to be `%s`, got: %s\", expected.FailureInfo.Type, trinoErr.FailureInfo.Type)\n\t}\n\tif !strings.HasPrefix(trinoErr.FailureInfo.Message, expected.FailureInfo.Message) {\n\t\tt.Fatalf(\"expected ErrTrino.FailureInfo.Message to start with `%s`, got: %s\", expected.FailureInfo.Message, trinoErr.FailureInfo.Message)\n\t}\n}\n\ntype tpchRow struct {\n\tCustKey    int\n\tName       string\n\tAddress    string\n\tNationKey  int\n\tPhone      string\n\tAcctBal    float64\n\tMktSegment string\n\tComment    string\n}\n\nfunc TestIntegrationSelectTpch1000(t *testing.T) {\n\tdb := integrationOpen(t)\n\tdefer db.Close()\n\trows, err := db.Query(\"SELECT * FROM tpch.sf1.customer LIMIT 1000\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer rows.Close()\n\tcount := 0\n\tfor rows.Next() {\n\t\tcount++\n\t\tvar col tpchRow\n\t\terr = rows.Scan(\n\t\t\t&col.CustKey,\n\t\t\t&col.Name,\n\t\t\t&col.Address,\n\t\t\t&col.NationKey,\n\t\t\t&col.Phone,\n\t\t\t&col.AcctBal,\n\t\t\t&col.MktSegment,\n\t\t\t&col.Comment,\n\t\t)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\t/*\n\t\t\tif col.CustKey == 1 && col.AcctBal != 711.56 {\n\t\t\t\tt.Fatal(\"unexpected acctbal for custkey=1:\", col.AcctBal)\n\t\t\t}\n\t\t*/\n\t}\n\tif rows.Err() != nil {\n\t\tt.Fatal(err)\n\t}\n\tif count != 1000 {\n\t\tt.Fatal(\"not enough rows returned:\", count)\n\t}\n}\n\nfunc TestIntegrationSelectCancelQuery(t *testing.T) {\n\tdb := integrationOpen(t)\n\tdefer db.Close()\n\tdeadline := time.Now().Add(200 * time.Millisecond)\n\tctx, cancel := context.WithDeadline(context.Background(), deadline)\n\tdefer cancel()\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM tpch.sf1.customer\")\n\tif err != nil {\n\t\tgoto handleErr\n\t}\n\tdefer rows.Close()\n\tfor rows.Next() {\n\t\tvar col tpchRow\n\t\terr = rows.Scan(\n\t\t\t&col.CustKey,\n\t\t\t&col.Name,\n\t\t\t&col.Address,\n\t\t\t&col.NationKey,\n\t\t\t&col.Phone,\n\t\t\t&col.AcctBal,\n\t\t\t&col.MktSegment,\n\t\t\t&col.Comment,\n\t\t)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\tif err = rows.Err(); err == nil {\n\t\tt.Fatal(\"unexpected query with deadline succeeded\")\n\t}\nhandleErr:\n\terrmsg := err.Error()\n\tfor _, msg := range []string{\"cancel\", \"deadline\"} {\n\t\tif strings.Contains(errmsg, msg) {\n\t\t\treturn\n\t\t}\n\t}\n\tt.Fatal(\"unexpected error:\", err)\n}\n\nfunc TestIntegrationSessionProperties(t *testing.T) {\n\tdsn := *integrationServerFlag\n\tdsn += \"?session_properties=query_max_run_time%3A10m%3Bquery_priority%3A2\"\n\tdb := integrationOpen(t, dsn)\n\tdefer db.Close()\n\trows, err := db.Query(\"SHOW SESSION\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tfor rows.Next() {\n\t\tcol := struct {\n\t\t\tName        string\n\t\t\tValue       string\n\t\t\tDefault     string\n\t\t\tType        string\n\t\t\tDescription string\n\t\t}{}\n\t\terr = rows.Scan(\n\t\t\t&col.Name,\n\t\t\t&col.Value,\n\t\t\t&col.Default,\n\t\t\t&col.Type,\n\t\t\t&col.Description,\n\t\t)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tswitch {\n\t\tcase col.Name == \"query_max_run_time\" && col.Value != \"10m\":\n\t\t\tt.Fatal(\"unexpected value for query_max_run_time:\", col.Value)\n\t\tcase col.Name == \"query_priority\" && col.Value != \"2\":\n\t\t\tt.Fatal(\"unexpected value for query_priority:\", col.Value)\n\t\t}\n\t}\n\tif err = rows.Err(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestIntegrationTypeConversion(t *testing.T) {\n\terr := RegisterCustomClient(\"uncompressed\", &http.Client{Transport: &http.Transport{DisableCompression: true}})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdsn := *integrationServerFlag\n\tdsn += \"?custom_client=uncompressed\"\n\tdb := integrationOpen(t, dsn)\n\tvar (\n\t\tgoTime            time.Time\n\t\tnullTime          NullTime\n\t\tgoBytes           []byte\n\t\tnullBytes         []byte\n\t\tgoString          string\n\t\tnullString        sql.NullString\n\t\tnullStringSlice   NullSliceString\n\t\tnullStringSlice2  NullSlice2String\n\t\tnullStringSlice3  NullSlice3String\n\t\tnullInt64Slice    NullSliceInt64\n\t\tnullInt64Slice2   NullSlice2Int64\n\t\tnullInt64Slice3   NullSlice3Int64\n\t\tnullFloat64Slice  NullSliceFloat64\n\t\tnullFloat64Slice2 NullSlice2Float64\n\t\tnullFloat64Slice3 NullSlice3Float64\n\t\tgoMap             map[string]interface{}\n\t\tnullMap           NullMap\n\t\tgoRow             []interface{}\n\t)\n\terr = db.QueryRow(`\n\t\tSELECT\n\t\t\tTIMESTAMP '2017-07-10 01:02:03.004 UTC',\n\t\t\tCAST(NULL AS TIMESTAMP),\n\t\t\tCAST(X'FFFF0FFF3FFFFFFF' AS VARBINARY),\n\t\t\tCAST(NULL AS VARBINARY),\n\t\t\tCAST('string' AS VARCHAR),\n\t\t\tCAST(NULL AS VARCHAR),\n\t\t\tARRAY['A', 'B', NULL],\n\t\t\tARRAY[ARRAY['A'], NULL],\n\t\t\tARRAY[ARRAY[ARRAY['A'], NULL], NULL],\n\t\t\tARRAY[1, 2, NULL],\n\t\t\tARRAY[ARRAY[1, 1, 1], NULL],\n\t\t\tARRAY[ARRAY[ARRAY[1, 1, 1], NULL], NULL],\n\t\t\tARRAY[1.0, 2.0, NULL],\n\t\t\tARRAY[ARRAY[1.1, 1.1, 1.1], NULL],\n\t\t\tARRAY[ARRAY[ARRAY[1.1, 1.1, 1.1], NULL], NULL],\n\t\t\tMAP(ARRAY['a', 'b'], ARRAY['c', 'd']),\n\t\t\tCAST(NULL AS MAP(ARRAY(INTEGER), ARRAY(INTEGER))),\n\t\t\tROW(1, 'a', CAST('2017-07-10 01:02:03.004 UTC' AS TIMESTAMP(6) WITH TIME ZONE), ARRAY['c'])\n\t`).Scan(\n\t\t&goTime,\n\t\t&nullTime,\n\t\t&goBytes,\n\t\t&nullBytes,\n\t\t&goString,\n\t\t&nullString,\n\t\t&nullStringSlice,\n\t\t&nullStringSlice2,\n\t\t&nullStringSlice3,\n\t\t&nullInt64Slice,\n\t\t&nullInt64Slice2,\n\t\t&nullInt64Slice3,\n\t\t&nullFloat64Slice,\n\t\t&nullFloat64Slice2,\n\t\t&nullFloat64Slice3,\n\t\t&goMap,\n\t\t&nullMap,\n\t\t&goRow,\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Compare the actual and expected values.\n\texpectedTime := time.Date(2017, 7, 10, 1, 2, 3, 4*1000000, time.UTC)\n\tif !goTime.Equal(expectedTime) {\n\t\tt.Errorf(\"expected GoTime to be %v, got %v\", expectedTime, goTime)\n\t}\n\n\texpectedBytes := []byte{0xff, 0xff, 0x0f, 0xff, 0x3f, 0xff, 0xff, 0xff}\n\tif !bytes.Equal(goBytes, expectedBytes) {\n\t\tt.Errorf(\"expected GoBytes to be %v, got %v\", expectedBytes, goBytes)\n\t}\n\n\tif nullBytes != nil {\n\t\tt.Errorf(\"expected NullBytes to be nil, got %v\", nullBytes)\n\t}\n\n\tif goString != \"string\" {\n\t\tt.Errorf(\"expected GoString to be %q, got %q\", \"string\", goString)\n\t}\n\n\tif nullString.Valid {\n\t\tt.Errorf(\"expected NullString.Valid to be false, got true\")\n\t}\n\n\tif !reflect.DeepEqual(nullStringSlice.SliceString, []sql.NullString{{String: \"A\", Valid: true}, {String: \"B\", Valid: true}, {Valid: false}}) {\n\t\tt.Errorf(\"expected NullStringSlice.SliceString to be %v, got %v\",\n\t\t\t[]sql.NullString{{String: \"A\", Valid: true}, {String: \"B\", Valid: true}, {Valid: false}},\n\t\t\tnullStringSlice.SliceString)\n\t}\n\tif !nullStringSlice.Valid {\n\t\tt.Errorf(\"expected NullStringSlice.Valid to be true, got false\")\n\t}\n\n\texpectedSlice2String := [][]sql.NullString{{{String: \"A\", Valid: true}}, {}}\n\tif !reflect.DeepEqual(nullStringSlice2.Slice2String, expectedSlice2String) {\n\t\tt.Errorf(\"expected NullStringSlice2.Slice2String to be %v, got %v\", expectedSlice2String, nullStringSlice2.Slice2String)\n\t}\n\tif !nullStringSlice2.Valid {\n\t\tt.Errorf(\"expected NullStringSlice2.Valid to be true, got false\")\n\t}\n\n\texpectedSlice3String := [][][]sql.NullString{{{{String: \"A\", Valid: true}}, {}}, {}}\n\tif !reflect.DeepEqual(nullStringSlice3.Slice3String, expectedSlice3String) {\n\t\tt.Errorf(\"expected NullStringSlice3.Slice3String to be %v, got %v\", expectedSlice3String, nullStringSlice3.Slice3String)\n\t}\n\tif !nullStringSlice3.Valid {\n\t\tt.Errorf(\"expected NullStringSlice3.Valid to be true, got false\")\n\t}\n\n\texpectedSliceInt64 := []sql.NullInt64{{Int64: 1, Valid: true}, {Int64: 2, Valid: true}, {Valid: false}}\n\tif !reflect.DeepEqual(nullInt64Slice.SliceInt64, expectedSliceInt64) {\n\t\tt.Errorf(\"expected NullInt64Slice.SliceInt64 to be %v, got %v\", expectedSliceInt64, nullInt64Slice.SliceInt64)\n\t}\n\tif !nullInt64Slice.Valid {\n\t\tt.Errorf(\"expected NullInt64Slice.Valid to be true, got false\")\n\t}\n\n\texpectedSlice2Int64 := [][]sql.NullInt64{{{Int64: 1, Valid: true}, {Int64: 1, Valid: true}, {Int64: 1, Valid: true}}, {}}\n\tif !reflect.DeepEqual(nullInt64Slice2.Slice2Int64, expectedSlice2Int64) {\n\t\tt.Errorf(\"expected NullInt64Slice2.Slice2Int64 to be %v, got %v\", expectedSlice2Int64, nullInt64Slice2.Slice2Int64)\n\t}\n\tif !nullInt64Slice2.Valid {\n\t\tt.Errorf(\"expected NullInt64Slice2.Valid to be true, got false\")\n\t}\n\n\texpectedSlice3Int64 := [][][]sql.NullInt64{{{{Int64: 1, Valid: true}, {Int64: 1, Valid: true}, {Int64: 1, Valid: true}}, {}}, {}}\n\tif !reflect.DeepEqual(nullInt64Slice3.Slice3Int64, expectedSlice3Int64) {\n\t\tt.Errorf(\"expected NullInt64Slice3.Slice3Int64 to be %v, got %v\", expectedSlice3Int64, nullInt64Slice3.Slice3Int64)\n\t}\n\tif !nullInt64Slice3.Valid {\n\t\tt.Errorf(\"expected NullInt64Slice3.Valid to be true, got false\")\n\t}\n\n\texpectedSliceFloat64 := []sql.NullFloat64{{Float64: 1.0, Valid: true}, {Float64: 2.0, Valid: true}, {Valid: false}}\n\tif !reflect.DeepEqual(nullFloat64Slice.SliceFloat64, expectedSliceFloat64) {\n\t\tt.Errorf(\"expected NullFloat64Slice.SliceFloat64 to be %v, got %v\", expectedSliceFloat64, nullFloat64Slice.SliceFloat64)\n\t}\n\tif !nullFloat64Slice.Valid {\n\t\tt.Errorf(\"expected NullFloat64Slice.Valid to be true, got false\")\n\t}\n\n\texpectedSlice2Float64 := [][]sql.NullFloat64{{{Float64: 1.1, Valid: true}, {Float64: 1.1, Valid: true}, {Float64: 1.1, Valid: true}}, {}}\n\tif !reflect.DeepEqual(nullFloat64Slice2.Slice2Float64, expectedSlice2Float64) {\n\t\tt.Errorf(\"expected NullFloat64Slice2.Slice2Float64 to be %v, got %v\", expectedSlice2Float64, nullFloat64Slice2.Slice2Float64)\n\t}\n\tif !nullFloat64Slice2.Valid {\n\t\tt.Errorf(\"expected NullFloat64Slice2.Valid to be true, got false\")\n\t}\n\n\texpectedSlice3Float64 := [][][]sql.NullFloat64{{{{Float64: 1.1, Valid: true}, {Float64: 1.1, Valid: true}, {Float64: 1.1, Valid: true}}, {}}, {}}\n\tif !reflect.DeepEqual(nullFloat64Slice3.Slice3Float64, expectedSlice3Float64) {\n\t\tt.Errorf(\"expected NullFloat64Slice3.Slice3Float64 to be %v, got %v\", expectedSlice3Float64, nullFloat64Slice3.Slice3Float64)\n\t}\n\tif !nullFloat64Slice3.Valid {\n\t\tt.Errorf(\"expected NullFloat64Slice3.Valid to be true, got false\")\n\t}\n\n\texpectedMap := map[string]interface{}{\"a\": \"c\", \"b\": \"d\"}\n\tif !reflect.DeepEqual(goMap, expectedMap) {\n\t\tt.Errorf(\"expected GoMap to be %v, got %v\", expectedMap, goMap)\n\t}\n\n\tif nullMap.Valid {\n\t\tt.Errorf(\"expected NullMap.Valid to be false, got true\")\n\t}\n\n\texpectedRow := []interface{}{json.Number(\"1\"), \"a\", \"2017-07-10 01:02:03.004000 UTC\", []interface{}{\"c\"}}\n\tif !reflect.DeepEqual(goRow, expectedRow) {\n\t\tt.Errorf(\"expected GoRow to be %v, got %v\", expectedRow, goRow)\n\t}\n}\n\nfunc TestComplexTypes(t *testing.T) {\n\t// This test has been created to showcase some issues with parsing\n\t// complex types. It is not intended to be a comprehensive test of\n\t// the parsing logic, but rather to provide a reference for future\n\t// changes to the parsing logic.\n\t//\n\t// The current implementation of the parsing logic reads the value\n\t// in the same format as the JSON response from Trino. This means\n\t// that we don't go further to parse values as their structured types.\n\t// For example, a row like `ROW(1, X'0000')` is read as\n\t// a list of a `json.Number(1)` and a base64-encoded string.\n\tt.Skip(\"skipping failing test\")\n\n\tdsn := *integrationServerFlag\n\tdb := integrationOpen(t, dsn)\n\n\tfor _, tt := range []struct {\n\t\tname     string\n\t\tquery    string\n\t\texpected interface{}\n\t}{\n\t\t{\n\t\t\tname:     \"row containing scalar values\",\n\t\t\tquery:    `SELECT ROW(1, 'a', X'0000')`,\n\t\t\texpected: []interface{}{1, \"a\", []byte{0x00, 0x00}},\n\t\t},\n\t\t{\n\t\t\tname:     \"nested row\",\n\t\t\tquery:    `SELECT ROW(ROW(1, 'a'), ROW(2, 'b'))`,\n\t\t\texpected: []interface{}{[]interface{}{1, \"a\"}, []interface{}{2, \"b\"}},\n\t\t},\n\t\t{\n\t\t\tname:     \"map with scalar values\",\n\t\t\tquery:    `SELECT MAP(ARRAY['a', 'b'], ARRAY[1, 2])`,\n\t\t\texpected: map[string]interface{}{\"a\": 1, \"b\": 2},\n\t\t},\n\t\t{\n\t\t\tname:     \"map with nested row\",\n\t\t\tquery:    `SELECT MAP(ARRAY['a', 'b'], ARRAY[ROW(1, 'a'), ROW(2, 'b')])`,\n\t\t\texpected: map[string]interface{}{\"a\": []interface{}{1, \"a\"}, \"b\": []interface{}{2, \"b\"}},\n\t\t},\n\t} {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar result interface{}\n\t\t\terr := db.QueryRow(tt.query).Scan(&result)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tif !reflect.DeepEqual(result, tt.expected) {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", tt.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIntegrationArgsConversion(t *testing.T) {\n\tdsn := *integrationServerFlag\n\tdb := integrationOpen(t, dsn)\n\tvalue := 0\n\terr := db.QueryRow(`\n\t\tSELECT 1 FROM (VALUES (\n\t\t\tCAST(1 AS TINYINT),\n\t\t\tCAST(1 AS SMALLINT),\n\t\t\tCAST(1 AS INTEGER),\n\t\t\tCAST(1 AS BIGINT),\n\t\t\tCAST(1 AS REAL),\n\t\t\tCAST(1 AS DOUBLE),\n\t\t\tTIMESTAMP '2017-07-10 01:02:03.004 UTC',\n\t\t\tCAST('string' AS VARCHAR),\n\t\t\tCAST(X'FFFF0FFF3FFFFFFF' AS VARBINARY),\n\t\t\tARRAY['A', 'B']\n\t\t\t)) AS t(col_tiny, col_small, col_int, col_big, col_real, col_double, col_ts, col_varchar, col_varbinary, col_array )\n\t\t\tWHERE 1=1\n\t\t\tAND col_tiny = ?\n\t\t\tAND col_small = ?\n\t\t\tAND col_int = ?\n\t\t\tAND col_big = ?\n\t\t\tAND col_real = cast(? as real)\n\t\t\tAND col_double = cast(? as double)\n\t\t\tAND col_ts = ?\n\t\t\tAND col_varchar = ?\n\t\t\tAND col_varbinary = ?\n\t\t\tAND col_array = ?`,\n\t\tint16(1),\n\t\tint16(1),\n\t\tint32(1),\n\t\tint64(1),\n\t\tNumeric(\"1\"),\n\t\tNumeric(\"1\"),\n\t\ttime.Date(2017, 7, 10, 1, 2, 3, 4*1000000, time.UTC),\n\t\t\"string\",\n\t\t[]byte{0xff, 0xff, 0x0f, 0xff, 0x3f, 0xff, 0xff, 0xff},\n\t\t[]string{\"A\", \"B\"},\n\t).Scan(&value)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestIntegrationNoResults(t *testing.T) {\n\tdb := integrationOpen(t)\n\trows, err := db.Query(\"SELECT 1 LIMIT 0\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tfor rows.Next() {\n\t\tt.Fatal(errors.New(\"Rows returned\"))\n\t}\n\tif err = rows.Err(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\nfunc TestRoleHeaderSupport(t *testing.T) {\n\tversion, err := strconv.Atoi(*trinoImageTagFlag)\n\tif (err != nil && *trinoImageTagFlag != \"latest\") || (err == nil && version < 458) {\n\t\tt.Skip(\"Skipping test when not using Trino 458 or later.\")\n\t}\n\ttests := []struct {\n\t\tname         string\n\t\tconfig       Config\n\t\trawDSN       string\n\t\tquery        string\n\t\texpectError  bool\n\t\terrorSubstr  string\n\t\tvalidateRows func(t *testing.T, rows *sql.Rows)\n\t}{\n\t\t{\n\t\t\tname: \"Valid hive admin role via Config\",\n\t\t\tconfig: Config{\n\t\t\t\tServerURI: *integrationServerFlag,\n\t\t\t\tRoles:     map[string]string{\"hive\": \"admin\"},\n\t\t\t},\n\t\t\tquery:       \"SHOW ROLES FROM hive\",\n\t\t\texpectError: false,\n\t\t\tvalidateRows: func(t *testing.T, rows *sql.Rows) {\n\t\t\t\tfoundAdmin := false\n\t\t\t\tfor rows.Next() {\n\t\t\t\t\tvar roleName string\n\t\t\t\t\terr := rows.Scan(&roleName)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tif roleName == \"admin\" {\n\t\t\t\t\t\tfoundAdmin = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trequire.True(t, foundAdmin, \"Expected to find 'admin' role in SHOW ROLES output\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tconfig: Config{\n\t\t\t\tServerURI: *integrationServerFlag,\n\t\t\t\tRoles:     map[string]string{\"tpch\": \"NONE\", \"memory\": \"ALL\"},\n\t\t\t},\n\t\t\tquery:       \"SELECT 1\",\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid special roles via Config\",\n\t\t\tconfig: Config{\n\t\t\t\tServerURI: *integrationServerFlag,\n\t\t\t\tRoles:     map[string]string{\"tpch\": \"NONE\", \"memory\": \"ALL\"},\n\t\t\t},\n\t\t\tquery:       \"SELECT 1\",\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"Valid hive admin role via DSN, not encoded url\",\n\t\t\trawDSN:      *integrationServerFlag + \"?roles=hive:admin\",\n\t\t\tquery:       \"SHOW ROLES FROM hive\",\n\t\t\texpectError: false,\n\t\t\tvalidateRows: func(t *testing.T, rows *sql.Rows) {\n\t\t\t\tfoundAdmin := false\n\t\t\t\tfor rows.Next() {\n\t\t\t\t\tvar roleName string\n\t\t\t\t\terr := rows.Scan(&roleName)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tif roleName == \"admin\" {\n\t\t\t\t\t\tfoundAdmin = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trequire.True(t, foundAdmin, \"Expected to find 'admin' role in SHOW ROLES output\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:        \"Valid roles via DSN, url encoded\",\n\t\t\trawDSN:      *integrationServerFlag + \"?roles=hive:admin\",\n\t\t\tquery:       \"SHOW ROLES FROM hive\",\n\t\t\texpectError: false,\n\t\t\tvalidateRows: func(t *testing.T, rows *sql.Rows) {\n\t\t\t\tfoundAdmin := false\n\t\t\t\tfor rows.Next() {\n\t\t\t\t\tvar roleName string\n\t\t\t\t\terr := rows.Scan(&roleName)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tif roleName == \"admin\" {\n\t\t\t\t\t\tfoundAdmin = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trequire.True(t, foundAdmin, \"Expected to find 'admin' role in SHOW ROLES output\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"No role - should fail to show roles\",\n\t\t\tconfig: Config{\n\t\t\t\tServerURI: *integrationServerFlag,\n\t\t\t},\n\t\t\tquery:       \"SHOW ROLES FROM hive\",\n\t\t\texpectError: true,\n\t\t\terrorSubstr: \"Access Denied\",\n\t\t},\n\t\t{\n\t\t\tname: \"Wrong role - should fail to show roles\",\n\t\t\tconfig: Config{\n\t\t\t\tServerURI: *integrationServerFlag,\n\t\t\t\tRoles:     map[string]string{\"hive\": \"ALL\"},\n\t\t\t},\n\t\t\tquery:       \"SHOW ROLES FROM hive\",\n\t\t\texpectError: true,\n\t\t\terrorSubstr: \"Access Denied\",\n\t\t},\n\t\t{\n\t\t\tname: \"Non-existent catalog role\",\n\t\t\tconfig: Config{\n\t\t\t\tServerURI: *integrationServerFlag,\n\t\t\t\tRoles:     map[string]string{\"not-exist-catalog\": \"role1\"},\n\t\t\t},\n\t\t\tquery:       \"SELECT 1\",\n\t\t\texpectError: true,\n\t\t\terrorSubstr: \"USER_ERROR: Catalog\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar dns string\n\t\t\tvar err error\n\n\t\t\tif tt.rawDSN != \"\" {\n\t\t\t\tdns = tt.rawDSN\n\t\t\t} else {\n\t\t\t\tdns, err = tt.config.FormatDSN()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdb := integrationOpen(t, dns)\n\t\t\tdefer db.Close()\n\n\t\t\trows, err := db.Query(tt.query)\n\n\t\t\tif tt.expectError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tif tt.errorSubstr != \"\" {\n\t\t\t\t\trequire.Contains(t, err.Error(), tt.errorSubstr)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tif tt.validateRows != nil && rows != nil {\n\t\t\t\t\tdefer rows.Close()\n\t\t\t\t\ttt.validateRows(t, rows)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIntegrationQueryParametersSelect(t *testing.T) {\n\tscenarios := []struct {\n\t\tname          string\n\t\tquery         string\n\t\targs          []interface{}\n\t\texpectedError error\n\t\texpectedRows  int\n\t}{\n\t\t{\n\t\t\tname:         \"valid string as varchar\",\n\t\t\tquery:        \"SELECT * FROM system.runtime.nodes WHERE system.runtime.nodes.node_id=?\",\n\t\t\targs:         []interface{}{\"test\"},\n\t\t\texpectedRows: 1,\n\t\t},\n\t\t{\n\t\t\tname:         \"valid int as bigint\",\n\t\t\tquery:        \"SELECT * FROM tpch.sf1.customer WHERE custkey=? LIMIT 2\",\n\t\t\targs:         []interface{}{int(1)},\n\t\t\texpectedRows: 1,\n\t\t},\n\t\t{\n\t\t\tname:          \"invalid string as bigint\",\n\t\t\tquery:         \"SELECT * FROM tpch.sf1.customer WHERE custkey=? LIMIT 2\",\n\t\t\targs:          []interface{}{\"1\"},\n\t\t\texpectedError: errors.New(`trino: query failed (200 OK): \"USER_ERROR: line 1:46: Cannot apply operator: bigint = varchar(1)\"`),\n\t\t},\n\t\t{\n\t\t\tname:          \"valid string as date\",\n\t\t\tquery:         \"SELECT * FROM tpch.sf1.lineitem WHERE shipdate=? LIMIT 2\",\n\t\t\targs:          []interface{}{\"1995-01-27\"},\n\t\t\texpectedError: errors.New(`trino: query failed (200 OK): \"USER_ERROR: line 1:47: Cannot apply operator: date = varchar(10)\"`),\n\t\t},\n\t}\n\n\tfor i := range scenarios {\n\t\tscenario := scenarios[i]\n\n\t\tt.Run(scenario.name, func(t *testing.T) {\n\t\t\tdb := integrationOpen(t)\n\t\t\tdefer db.Close()\n\n\t\t\trows, err := db.Query(scenario.query, scenario.args...)\n\t\t\tif err != nil {\n\t\t\t\tif scenario.expectedError == nil {\n\t\t\t\t\tt.Errorf(\"Unexpected err: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif err.Error() == scenario.expectedError.Error() {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Expected err to be %s but got %s\", scenario.expectedError, err)\n\t\t\t}\n\n\t\t\tif scenario.expectedError != nil {\n\t\t\t\tt.Error(\"missing expected error\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdefer rows.Close()\n\n\t\t\tvar count int\n\t\t\tfor rows.Next() {\n\t\t\t\tcount++\n\t\t\t}\n\t\t\tif err = rows.Err(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif count != scenario.expectedRows {\n\t\t\t\tt.Errorf(\"expecting %d rows, got %d\", scenario.expectedRows, count)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIntegrationQueryNextAfterClose(t *testing.T) {\n\t// NOTE: This is testing invalid behaviour. It ensures that we don't\n\t// panic if we call driverRows.Next after we closed the driverStmt.\n\n\tctx := context.Background()\n\tconn, err := (&Driver{}).Open(*integrationServerFlag)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to open connection: %v\", err)\n\t}\n\tdefer conn.Close()\n\n\tstmt, err := conn.(driver.ConnPrepareContext).PrepareContext(ctx, \"SELECT 1\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed preparing query: %v\", err)\n\t}\n\n\trows, err := stmt.(driver.StmtQueryContext).QueryContext(ctx, []driver.NamedValue{})\n\tif err != nil {\n\t\tt.Fatalf(\"Failed running query: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\tstmt.Close() // NOTE: the important bit.\n\n\tvar result driver.Value\n\tif err := rows.Next([]driver.Value{result}); err != nil && !spoolingProtocolSupported {\n\t\tt.Fatalf(\"unexpected result: %+v, no error was expected\", err)\n\t}\n\tif err := rows.Next([]driver.Value{result}); err != io.EOF {\n\t\tt.Fatalf(\"unexpected result: %+v, expected io.EOF\", err)\n\t}\n}\n\nfunc TestIntegrationExec(t *testing.T) {\n\tdb := integrationOpen(t)\n\tdefer db.Close()\n\n\t_, err := db.Query(`SELECT count(*) FROM nation`)\n\texpected := \"Schema must be specified when session schema is not set\"\n\tif err == nil || !strings.Contains(err.Error(), expected) {\n\t\tt.Fatalf(\"Expected to fail to execute query with error: %v, got: %v\", expected, err)\n\t}\n\n\tresult, err := db.Exec(\"USE tpch.sf100\")\n\tif err != nil {\n\t\tt.Fatal(\"Failed executing query:\", err.Error())\n\t}\n\tif result == nil {\n\t\tt.Fatal(\"Expected exec result to be not nil\")\n\t}\n\n\ta, err := result.RowsAffected()\n\tif err != nil {\n\t\tt.Fatal(\"Expected RowsAffected not to return any error, got:\", err)\n\t}\n\tif a != 0 {\n\t\tt.Fatal(\"Expected RowsAffected to be zero, got:\", a)\n\t}\n\trows, err := db.Query(`SELECT count(*) FROM nation`)\n\tif err != nil {\n\t\tt.Fatal(\"Failed executing query:\", err.Error())\n\t}\n\tif rows == nil || !rows.Next() {\n\t\tt.Fatal(\"Failed fetching results\")\n\t}\n}\n\nfunc TestIntegrationUnsupportedHeader(t *testing.T) {\n\tdsn := *integrationServerFlag\n\tdsn += \"?catalog=tpch&schema=sf10\"\n\tdb := integrationOpen(t, dsn)\n\tdefer db.Close()\n\tcases := []struct {\n\t\tquery string\n\t\terr   error\n\t}{\n\t\t{\n\t\t\tquery: \"SET ROLE dummy\",\n\t\t\terr:   errors.New(`trino: query failed (200 OK): \"USER_ERROR: line 1:1: Role 'dummy' does not exist\"`),\n\t\t},\n\t\t{\n\t\t\tquery: \"SET PATH dummy\",\n\t\t\terr:   errors.New(`trino: query failed (200 OK): \"USER_ERROR: SET PATH not supported by client\"`),\n\t\t},\n\t}\n\tfor _, c := range cases {\n\t\t_, err := db.Query(c.query)\n\t\tif err == nil || err.Error() != c.err.Error() {\n\t\t\tt.Fatal(\"unexpected error:\", err)\n\t\t}\n\t}\n}\n\nfunc TestSpoolingWorkersHigherThenAllowedOutOfOrderSegments(t *testing.T) {\n\tif !spoolingProtocolSupported {\n\t\tt.Skip(\"Skipping test when spooling protocol is not supported.\")\n\t}\n\tdb := integrationOpen(t)\n\tdefer db.Close()\n\n\texpectedError := \"spooling worker cannot be greater than max out of order segments allowed. spooling workers: 2, allowed out of order segments: 1\"\n\t_, err := db.Query(\"SELECT 1\",\n\t\tsql.Named(trinoEncoding, \"json\"),\n\t\tsql.Named(trinoSpoolingWorkerCount, \"2\"),\n\t\tsql.Named(trinoMaxOutOfOrdersSegments, \"1\"))\n\n\tif err == nil || err.Error() != expectedError {\n\t\tt.Fatal(\"unexpected error:\", err)\n\t}\n}\n\nfunc TestIntegrationQueryContext(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\ttimeout        time.Duration\n\t\texpectedErrMsg string\n\t}{\n\t\t{\n\t\t\tname:           \"Context Cancellation\",\n\t\t\ttimeout:        0,\n\t\t\texpectedErrMsg: \"canceled\",\n\t\t},\n\t\t{\n\t\t\tname:           \"Context Deadline Exceeded\",\n\t\t\ttimeout:        3 * time.Second,\n\t\t\texpectedErrMsg: \"context deadline exceeded\",\n\t\t},\n\t}\n\n\tif err := RegisterCustomClient(\"uncompressed\", &http.Client{Transport: &http.Transport{DisableCompression: true}}); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdsn := *integrationServerFlag + \"?catalog=tpch&schema=sf100&source=cancel-test&custom_client=uncompressed\"\n\tdb := integrationOpen(t, dsn)\n\tdefer db.Close()\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar ctx context.Context\n\t\t\tvar cancel context.CancelFunc\n\n\t\t\tif tt.timeout == 0 {\n\t\t\t\tctx, cancel = context.WithCancel(context.Background())\n\t\t\t} else {\n\t\t\t\tctx, cancel = context.WithTimeout(context.Background(), tt.timeout)\n\t\t\t}\n\t\t\tdefer cancel()\n\n\t\t\terrCh := make(chan error, 1)\n\t\t\tdone := make(chan struct{})\n\t\t\tlongQuery := \"SELECT COUNT(*) FROM lineitem\"\n\n\t\t\tgo func() {\n\t\t\t\t// query will complete in ~7s unless cancelled\n\t\t\t\trows, err := db.QueryContext(ctx, longQuery)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrCh <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tdefer rows.Close()\n\n\t\t\t\trows.Next()\n\t\t\t\tif err = rows.Err(); err != nil {\n\t\t\t\t\terrCh <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tclose(done)\n\t\t\t}()\n\n\t\t\t// Poll system.runtime.queries to get the query ID\n\t\t\tvar queryID string\n\t\t\tpollCtx, pollCancel := context.WithTimeout(context.Background(), 1*time.Second)\n\t\t\tdefer pollCancel()\n\n\t\t\tfor {\n\t\t\t\trow := db.QueryRowContext(pollCtx, \"SELECT query_id FROM system.runtime.queries WHERE state = 'RUNNING' AND source = 'cancel-test' AND query = ?\", longQuery)\n\t\t\t\terr := row.Scan(&queryID)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif err != sql.ErrNoRows {\n\t\t\t\t\tt.Fatal(\"failed to read query ID:\", err)\n\t\t\t\t}\n\t\t\t\tif err = contextSleep(pollCtx, 100*time.Millisecond); err != nil {\n\t\t\t\t\tt.Fatal(\"query did not start in 1 second\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif tt.timeout == 0 {\n\t\t\t\tcancel()\n\t\t\t}\n\n\t\t\t// Wait for the query to be canceled or completed\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\t\tt.Fatal(\"unexpected query succeeded despite cancellation or deadline\")\n\t\t\tcase err := <-errCh:\n\t\t\t\tif !strings.Contains(err.Error(), tt.expectedErrMsg) {\n\t\t\t\t\tt.Fatalf(\"expected error containing %q, but got: %v\", tt.expectedErrMsg, err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Poll system.runtime.queries to verify the query was canceled\n\t\t\tpollCtx, pollCancel = context.WithTimeout(context.Background(), 2*time.Second)\n\t\t\tdefer pollCancel()\n\n\t\t\tfor {\n\t\t\t\trow := db.QueryRowContext(pollCtx, \"SELECT state, error_code FROM system.runtime.queries WHERE query_id = ?\", queryID)\n\t\t\t\tvar state string\n\t\t\t\tvar code *string\n\t\t\t\terr := row.Scan(&state, &code)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(\"failed to read query state:\", err)\n\t\t\t\t}\n\t\t\t\tif state == \"FAILED\" && code != nil && *code == \"USER_CANCELED\" {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif err = contextSleep(pollCtx, 100*time.Millisecond); err != nil {\n\t\t\t\t\tt.Fatalf(\"query was not canceled in 2 seconds; state: %s, code: %v, err: %v\", state, code, err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIntegrationAccessToken(t *testing.T) {\n\tif tlsServer == \"\" {\n\t\tt.Skip(\"Skipping access token test when using a custom integration server.\")\n\t}\n\n\taccessToken, err := generateToken()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdsn := tlsServer + \"?accessToken=\" + accessToken\n\n\tdb := integrationOpen(t, dsn)\n\n\tdefer db.Close()\n\trows, err := db.Query(\"SHOW CATALOGS\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer rows.Close()\n\tcount := 0\n\tfor rows.Next() {\n\t\tcount++\n\t}\n\tif count < 1 {\n\t\tt.Fatal(\"not enough rows returned:\", count)\n\t}\n}\n\nfunc generateToken() (string, error) {\n\tprivateKeyPEM, err := os.ReadFile(\"etc/secrets/private_key.pem\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error reading private key file: %w\", err)\n\t}\n\n\tprivateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyPEM)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error parsing private key: %w\", err)\n\t}\n\n\t// Subject must be 'test'\n\tclaims := jwt.RegisteredClaims{\n\t\tExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * 365 * time.Hour)),\n\t\tIssuer:    \"gotrino\",\n\t\tSubject:   \"test\",\n\t}\n\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)\n\tsignedToken, err := token.SignedString(privateKey)\n\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error generating token: %w\", err)\n\t}\n\n\treturn signedToken, nil\n}\n\nfunc TestIntegrationTLS(t *testing.T) {\n\tif tlsServer == \"\" {\n\t\tt.Skip(\"Skipping TLS test when using a custom integration server.\")\n\t}\n\n\tdsn := tlsServer\n\tdb := integrationOpen(t, dsn)\n\n\tdefer db.Close()\n\trow := db.QueryRow(\"SELECT 1\")\n\tvar count int\n\tif err := row.Scan(&count); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif count != 1 {\n\t\tt.Fatal(\"unexpected count=\", count)\n\t}\n}\n\nfunc contextSleep(ctx context.Context, d time.Duration) error {\n\ttimer := time.NewTimer(100 * time.Millisecond)\n\tselect {\n\tcase <-timer.C:\n\t\treturn nil\n\tcase <-ctx.Done():\n\t\tif !timer.Stop() {\n\t\t\t<-timer.C\n\t\t}\n\t\treturn ctx.Err()\n\t}\n}\n\nfunc TestIntegrationDayToHourIntervalMilliPrecision(t *testing.T) {\n\tdb := integrationOpen(t)\n\tdefer db.Close()\n\ttests := []struct {\n\t\tname    string\n\t\targ     time.Duration\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid 1234567891s\",\n\t\t\targ:     time.Duration(1234567891) * time.Second,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"valid 123456789.1s\",\n\t\t\targ:     time.Duration(123456789100) * time.Millisecond,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"valid 12345678.91s\",\n\t\t\targ:     time.Duration(12345678910) * time.Millisecond,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"valid 1234567.891s\",\n\t\t\targ:     time.Duration(1234567891) * time.Millisecond,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"valid -1234567891s\",\n\t\t\targ:     time.Duration(-1234567891) * time.Second,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"valid -123456789.1s\",\n\t\t\targ:     time.Duration(-123456789100) * time.Millisecond,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"valid -12345678.91s\",\n\t\t\targ:     time.Duration(-12345678910) * time.Millisecond,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"valid -1234567.891s\",\n\t\t\targ:     time.Duration(-1234567891) * time.Millisecond,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid 1234567891.2s\",\n\t\t\targ:     time.Duration(1234567891200) * time.Millisecond,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid 123456789.12s\",\n\t\t\targ:     time.Duration(123456789120) * time.Millisecond,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid 12345678.912s\",\n\t\t\targ:     time.Duration(12345678912) * time.Millisecond,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid -1234567891.2s\",\n\t\t\targ:     time.Duration(-1234567891200) * time.Millisecond,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid -123456789.12s\",\n\t\t\targ:     time.Duration(-123456789120) * time.Millisecond,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid -12345678.912s\",\n\t\t\targ:     time.Duration(-12345678912) * time.Millisecond,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid max seconds (9223372036)\",\n\t\t\targ:     time.Duration(math.MaxInt64) / time.Second * time.Second,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid min seconds (-9223372036)\",\n\t\t\targ:     time.Duration(math.MinInt64) / time.Second * time.Second,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"valid max seconds (2147483647)\",\n\t\t\targ:  math.MaxInt32 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"valid min seconds (-2147483647)\",\n\t\t\targ:  -math.MaxInt32 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"valid max minutes (153722867)\",\n\t\t\targ:  time.Duration(math.MaxInt64) / time.Minute * time.Minute,\n\t\t},\n\t\t{\n\t\t\tname: \"valid min minutes (-153722867)\",\n\t\t\targ:  time.Duration(math.MinInt64) / time.Minute * time.Minute,\n\t\t},\n\t\t{\n\t\t\tname: \"valid max hours (2562047)\",\n\t\t\targ:  time.Duration(math.MaxInt64) / time.Hour * time.Hour,\n\t\t},\n\t\t{\n\t\t\tname: \"valid min hours (-2562047)\",\n\t\t\targ:  time.Duration(math.MinInt64) / time.Hour * time.Hour,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t_, err := db.Exec(\"SELECT ?\", test.arg)\n\t\t\tif (err != nil) != test.wantErr {\n\t\t\t\tt.Errorf(\"Exec() error = %v, wantErr %v\", err, test.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIntegrationLargeQuery(t *testing.T) {\n\tversion, err := strconv.Atoi(*trinoImageTagFlag)\n\tif (err != nil && *trinoImageTagFlag != \"latest\") || (err == nil && version < 418) {\n\t\tt.Skip(\"Skipping test when not using Trino 418 or later.\")\n\t}\n\tdsn := *integrationServerFlag\n\tdsn += \"?explicitPrepare=false\"\n\tdb := integrationOpen(t, dsn)\n\tdefer db.Close()\n\trows, err := db.Query(\"SELECT ?, '\"+strings.Repeat(\"a\", 5000000)+\"'\", 42)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer rows.Close()\n\tcount := 0\n\tfor rows.Next() {\n\t\tcount++\n\t}\n\tif rows.Err() != nil {\n\t\tt.Fatal(err)\n\t}\n\tif count != 1 {\n\t\tt.Fatal(\"not enough rows returned:\", count)\n\t}\n}\n\nfunc TestIntegrationTypeConversionSpoolingProtocolInlineJsonEncoder(t *testing.T) {\n\terr := RegisterCustomClient(\"uncompressed\", &http.Client{Transport: &http.Transport{DisableCompression: true}})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdsn := *integrationServerFlag\n\tdsn += \"?custom_client=uncompressed\"\n\tdb := integrationOpen(t, dsn)\n\tvar (\n\t\tgoTime            time.Time\n\t\tnullTime          NullTime\n\t\tgoString          string\n\t\tnullString        sql.NullString\n\t\tnullStringSlice   NullSliceString\n\t\tnullStringSlice2  NullSlice2String\n\t\tnullStringSlice3  NullSlice3String\n\t\tnullInt64Slice    NullSliceInt64\n\t\tnullInt64Slice2   NullSlice2Int64\n\t\tnullInt64Slice3   NullSlice3Int64\n\t\tnullFloat64Slice  NullSliceFloat64\n\t\tnullFloat64Slice2 NullSlice2Float64\n\t\tnullFloat64Slice3 NullSlice3Float64\n\t\tgoMap             map[string]interface{}\n\t\tnullMap           NullMap\n\t\tgoRow             []interface{}\n\t)\n\terr = db.QueryRow(`\n\t\tSELECT\n\t\t\tTIMESTAMP '2017-07-10 01:02:03.004 UTC',\n\t\t\tCAST(NULL AS TIMESTAMP),\n\t\t\tCAST('string' AS VARCHAR),\n\t\t\tCAST(NULL AS VARCHAR),\n\t\t\tARRAY['A', 'B', NULL],\n\t\t\tARRAY[ARRAY['A'], NULL],\n\t\t\tARRAY[ARRAY[ARRAY['A'], NULL], NULL],\n\t\t\tARRAY[1, 2, NULL],\n\t\t\tARRAY[ARRAY[1, 1, 1], NULL],\n\t\t\tARRAY[ARRAY[ARRAY[1, 1, 1], NULL], NULL],\n\t\t\tARRAY[1.0, 2.0, NULL],\n\t\t\tARRAY[ARRAY[1.1, 1.1, 1.1], NULL],\n\t\t\tARRAY[ARRAY[ARRAY[1.1, 1.1, 1.1], NULL], NULL],\n\t\t\tMAP(ARRAY['a', 'b'], ARRAY['c', 'd']),\n\t\t\tCAST(NULL AS MAP(ARRAY(INTEGER), ARRAY(INTEGER))),\n\t\t\tROW(1, 'a', CAST('2017-07-10 01:02:03.004 UTC' AS TIMESTAMP(6) WITH TIME ZONE), ARRAY['c'])\n\t`, sql.Named(trinoEncoding, \"json\")).Scan(\n\t\t&goTime,\n\t\t&nullTime,\n\t\t&goString,\n\t\t&nullString,\n\t\t&nullStringSlice,\n\t\t&nullStringSlice2,\n\t\t&nullStringSlice3,\n\t\t&nullInt64Slice,\n\t\t&nullInt64Slice2,\n\t\t&nullInt64Slice3,\n\t\t&nullFloat64Slice,\n\t\t&nullFloat64Slice2,\n\t\t&nullFloat64Slice3,\n\t\t&goMap,\n\t\t&nullMap,\n\t\t&goRow,\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestIntegrationSelectTpchSpoolingSegments(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tquery    string\n\t\tencoding string\n\t\texpected int\n\t}{\n\t\t// Testing with a LIMIT of 1001 rows.\n\t\t// Since we exceed the `protocol.spooling.inlining.max-rows` threshold (1000),\n\t\t// this query trigger spooling protocol with spooled segments.\n\t\t{\n\t\t\tname:     \"Spooled Segment JSON+ZSTD Encoded\",\n\t\t\tquery:    \"SELECT * FROM tpch.sf1.customer LIMIT 1001\",\n\t\t\tencoding: \"json+zstd\",\n\t\t\texpected: 1001,\n\t\t},\n\t\t{\n\t\t\tname:     \"Spooled Segment JSON Encoded\",\n\t\t\tquery:    \"SELECT * FROM tpch.sf1.customer LIMIT 1001\",\n\t\t\tencoding: \"json\",\n\t\t\texpected: 1001,\n\t\t},\n\t\t{\n\t\t\tname:     \"Spooled Segment JSON+LZ4 Encoded\",\n\t\t\tquery:    \"SELECT * FROM tpch.sf1.customer LIMIT 1001\",\n\t\t\tencoding: \"json+lz4\",\n\t\t\texpected: 1001,\n\t\t},\n\t\t// Testing with a LIMIT of 100 rows.\n\t\t// This should remain inline as it is below the `protocol.spooling.inlining.max-rows` (1000) and bellow `protocol.spooling.inlining.max-size` 128kb\n\t\t{\n\t\t\tname:     \"Inline Segment JSON+ZSTD Encoded\",\n\t\t\tquery:    \"SELECT * FROM tpch.sf1.customer LIMIT 100\",\n\t\t\tencoding: \"json+zstd\",\n\t\t\texpected: 100,\n\t\t},\n\t\t{\n\t\t\tname:     \"Inline Segment JSON+LZ4 Encoded\",\n\t\t\tquery:    \"SELECT * FROM tpch.sf1.customer LIMIT 100\",\n\t\t\tencoding: \"json+lz4\",\n\t\t\texpected: 100,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdb := integrationOpen(t)\n\t\t\tdefer db.Close()\n\n\t\t\trows, err := db.Query(tt.query, sql.Named(trinoEncoding, tt.encoding))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Query failed: %v\", err)\n\t\t\t}\n\t\t\tdefer rows.Close()\n\n\t\t\tcount := 0\n\t\t\tfor rows.Next() {\n\t\t\t\tcount++\n\t\t\t\tvar col tpchRow\n\t\t\t\terr = rows.Scan(\n\t\t\t\t\t&col.CustKey,\n\t\t\t\t\t&col.Name,\n\t\t\t\t\t&col.Address,\n\t\t\t\t\t&col.NationKey,\n\t\t\t\t\t&col.Phone,\n\t\t\t\t\t&col.AcctBal,\n\t\t\t\t\t&col.MktSegment,\n\t\t\t\t\t&col.Comment,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Row scan failed: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif rows.Err() != nil {\n\t\t\t\tt.Fatalf(\"Rows iteration error: %v\", rows.Err())\n\t\t\t}\n\n\t\t\tif count != tt.expected {\n\t\t\t\tt.Fatalf(\"Expected %d rows, got %d\", tt.expected, count)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSpoolingIntegrationOrderedResults(t *testing.T) {\n\tif !spoolingProtocolSupported {\n\t\tt.Skip(\"Skipping test when spooling protocol is not supported.\")\n\t}\n\tdb := integrationOpen(t)\n\tdefer db.Close()\n\n\tquery := `\n\t\tSELECT *\n\t\tFROM TABLE(sequence(\n\t\t\tstart => 1,\n\t\t\tstop => 5000000\n\t\t))\n\t\tORDER BY sequential_number\n\t`\n\n\trows, err := db.Query(query, sql.Named(trinoEncoding, \"json\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Query failed: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\texpected := 1\n\tvar actual int\n\n\tfor rows.Next() {\n\t\terr = rows.Scan(&actual)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Row scan failed: %v\", err)\n\t\t}\n\n\t\tif actual != expected {\n\t\t\tt.Fatalf(\"Unexpected number at position %d: got %d, expected %d\", expected, actual, expected)\n\t\t}\n\t\texpected++\n\t}\n\n\tif rows.Err() != nil {\n\t\tt.Fatalf(\"Rows iteration error: %v\", rows.Err())\n\t}\n\n\tif expected != 5_000_001 {\n\t\tt.Fatalf(\"Expected 5,000,000 rows, got %d\", expected-1)\n\t}\n}\n\nfunc TestDsnClientTags(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tdsnSuffix    string\n\t\tsource       string\n\t\texpectedTags []string\n\t}{\n\t\t{\n\t\t\tname:         \"Single tag\",\n\t\t\tdsnSuffix:    \"?clientTags=test&source=single-tag-test\",\n\t\t\tsource:       \"single-tag-test\",\n\t\t\texpectedTags: []string{\"test\"},\n\t\t},\n\t\t{\n\t\t\tname:         \"Multiple tags with special characters\",\n\t\t\tdsnSuffix:    \"?clientTags=foo+%2520%2Cbar%3Dtest%2Cbaz%23tag&source=multiple-tags-test-special-characters\",\n\t\t\tsource:       \"multiple-tags-test-special-characters\",\n\t\t\texpectedTags: []string{\"foo %20\", \"bar=test\", \"baz#tag\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdsn := *integrationServerFlag + tt.dsnSuffix\n\t\t\tdb := integrationOpen(t, dsn)\n\t\t\tdefer db.Close()\n\n\t\t\tquery := \"SELECT 1\"\n\t\t\trows, err := db.Query(query)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer rows.Close()\n\n\t\t\tif rows.Next() {\n\t\t\t}\n\n\t\t\tif err := rows.Err(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tvar queryID string\n\t\t\terr = db.QueryRowContext(context.Background(),\n\t\t\t\t\"SELECT query_id FROM system.runtime.queries WHERE source = ? AND query = ?\", tt.source, query,\n\t\t\t).Scan(&queryID)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tqueryInfo, err := getQueryInfo(dsn, queryID)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tif !reflect.DeepEqual(queryInfo.Session.ClientTags, tt.expectedTags) {\n\t\t\t\tt.Fatalf(\"Expected client tags %v, got %v\", tt.expectedTags, queryInfo.Session.ClientTags)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParametersClientTags(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tdsnSuffix    string\n\t\tTags         string\n\t\tsource       string\n\t\texpectedTags []string\n\t}{\n\t\t{\n\t\t\tname:         \"Single tag\",\n\t\t\tdsnSuffix:    \"?clientTags=query-parameter-single-tag-test&source=query-parameter-single-tag-test\",\n\t\t\tTags:         \"single-tag\",\n\t\t\tsource:       \"query-parameter-single-tag-test\",\n\t\t\texpectedTags: []string{\"single-tag\"},\n\t\t},\n\t\t{\n\t\t\tname:         \"Multiple tags with special characters\",\n\t\t\tdsnSuffix:    \"?clientTags=query-parameter-multiple-tags-test&source=query-parameter-multiple-tags-test\",\n\t\t\tTags:         \"foo %20,bar=test,baz#tag\",\n\t\t\tsource:       \"query-parameter-multiple-tags-test\",\n\t\t\texpectedTags: []string{\"foo %20\", \"bar=test\", \"baz#tag\"},\n\t\t},\n\t\t{\n\t\t\tname:         \"Override dsn tags\",\n\t\t\tdsnSuffix:    \"?clientTags=foo%2B%2520%3Bbar%3Dtest%3Bbaz%23tag&source=query-parameter-override-tags\",\n\t\t\tTags:         \"query-parameter-override-tag-test\",\n\t\t\tsource:       \"query-parameter-override-tags\",\n\t\t\texpectedTags: []string{\"query-parameter-override-tag-test\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdsn := *integrationServerFlag + tt.dsnSuffix\n\t\t\tdb := integrationOpen(t, dsn)\n\t\t\tdefer db.Close()\n\n\t\t\tquery := \"SELECT 1\"\n\t\t\trows, err := db.Query(query, sql.Named(trinoTagsHeader, tt.Tags))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer rows.Close()\n\n\t\t\tif rows.Next() {\n\t\t\t}\n\n\t\t\tif err := rows.Err(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tvar queryID string\n\t\t\terr = db.QueryRowContext(context.Background(),\n\t\t\t\t\"SELECT query_id FROM system.runtime.queries WHERE source = ? AND query = ?\", tt.source, query,\n\t\t\t).Scan(&queryID)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tqueryInfo, err := getQueryInfo(dsn, queryID)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tif !reflect.DeepEqual(queryInfo.Session.ClientTags, tt.expectedTags) {\n\t\t\t\tt.Fatalf(\"Expected client tags %v, got %v\", tt.expectedTags, queryInfo.Session.ClientTags)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype QuerySession struct {\n\tClientTags []string `json:\"clientTags\"`\n}\ntype QueryInfo struct {\n\tSession QuerySession `json:\"session\"`\n}\n\nfunc getQueryInfo(dsn, queryId string) (QueryInfo, error) {\n\n\tserverURL, err := url.Parse(dsn)\n\tif err != nil {\n\t\treturn QueryInfo{}, err\n\t}\n\tqueryInfoURL := serverURL.Scheme + \"://\" + serverURL.Host + \"/v1/query/\" + url.PathEscape(queryId)\n\n\treq, err := http.NewRequest(\"GET\", queryInfoURL, nil)\n\tif err != nil {\n\t\treturn QueryInfo{}, err\n\t}\n\treq.Header.Set(\"X-Trino-User\", serverURL.User.Username())\n\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\treturn QueryInfo{}, err\n\t}\n\n\tdefer resp.Body.Close()\n\n\tvar queryInfo QueryInfo\n\tif err := json.NewDecoder(resp.Body).Decode(&queryInfo); err != nil {\n\t\treturn QueryInfo{}, err\n\t}\n\n\treturn queryInfo, nil\n}\n"
  },
  {
    "path": "trino/serial.go",
    "content": "// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage trino\n\nimport (\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype UnsupportedArgError struct {\n\tt string\n}\n\nfunc (e UnsupportedArgError) Error() string {\n\treturn fmt.Sprintf(\"trino: unsupported arg type: %s\", e.t)\n}\n\n// Numeric is a string representation of a number, such as \"10\", \"5.5\" or in scientific form\n// If another string format is used it will error to serialise\ntype Numeric string\n\n// trinoDate represents a Date type in Trino.\ntype trinoDate struct {\n\tyear  int\n\tmonth time.Month\n\tday   int\n}\n\n// Date creates a representation of a Trino Date type.\nfunc Date(year int, month time.Month, day int) trinoDate {\n\treturn trinoDate{year, month, day}\n}\n\n// trinoTime represents a Time type in Trino.\ntype trinoTime struct {\n\thour       int\n\tminute     int\n\tsecond     int\n\tnanosecond int\n}\n\n// Time creates a representation of a Trino Time type. To represent time with precision higher than nanoseconds, pass the value as a string and use a cast in the query.\nfunc Time(hour int,\n\tminute int,\n\tsecond int,\n\tnanosecond int) trinoTime {\n\treturn trinoTime{hour, minute, second, nanosecond}\n}\n\n// trinoTimeTz represents a Time(9) With Timezone type in Trino.\ntype trinoTimeTz time.Time\n\n// TimeTz creates a representation of a Trino Time(9) With Timezone type.\nfunc TimeTz(hour int,\n\tminute int,\n\tsecond int,\n\tnanosecond int,\n\tlocation *time.Location) trinoTimeTz {\n\t// When reading a time, a nil location indicates UTC.\n\t// However, passing nil to time.Date() panics.\n\tif location == nil {\n\t\tlocation = time.UTC\n\t}\n\treturn trinoTimeTz(time.Date(0, 0, 0, hour, minute, second, nanosecond, location))\n}\n\n// Timestamp indicates we want a TimeStamp type WITHOUT a time zone in Trino from a Golang time.\ntype trinoTimestamp time.Time\n\n// Timestamp creates a representation of a Trino Timestamp(9) type.\nfunc Timestamp(year int,\n\tmonth time.Month,\n\tday int,\n\thour int,\n\tminute int,\n\tsecond int,\n\tnanosecond int) trinoTimestamp {\n\treturn trinoTimestamp(time.Date(year, month, day, hour, minute, second, nanosecond, time.UTC))\n}\n\n// Serial converts any supported value to its equivalent string for as a Trino parameter\n// See https://trino.io/docs/current/language/types.html\nfunc Serial(v interface{}) (string, error) {\n\tswitch x := v.(type) {\n\tcase nil:\n\t\treturn \"NULL\", nil\n\n\t// numbers convertible to int\n\tcase int8:\n\t\treturn strconv.Itoa(int(x)), nil\n\tcase int16:\n\t\treturn strconv.Itoa(int(x)), nil\n\tcase int32:\n\t\treturn strconv.Itoa(int(x)), nil\n\tcase int:\n\t\treturn strconv.Itoa(x), nil\n\tcase uint16:\n\t\treturn strconv.Itoa(int(x)), nil\n\n\tcase int64:\n\t\treturn strconv.FormatInt(x, 10), nil\n\n\tcase uint32:\n\t\treturn strconv.FormatUint(uint64(x), 10), nil\n\tcase uint:\n\t\treturn strconv.FormatUint(uint64(x), 10), nil\n\tcase uint64:\n\t\treturn strconv.FormatUint(x, 10), nil\n\n\t\t// float32, float64 not supported because digit precision will easily cause large problems\n\tcase float32:\n\t\treturn \"\", UnsupportedArgError{\"float32\"}\n\tcase float64:\n\t\treturn \"\", UnsupportedArgError{\"float64\"}\n\n\tcase Numeric:\n\t\tif _, err := strconv.ParseFloat(string(x), 64); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn string(x), nil\n\n\t\t// note byte and uint are not supported, this is because byte is an alias for uint8\n\t\t// if you were to use uint8 (as a number) it could be interpreted as a byte, so it is unsupported\n\t\t// use string instead of byte and any other uint/int type for uint8\n\tcase byte:\n\t\treturn \"\", UnsupportedArgError{\"byte/uint8\"}\n\n\tcase bool:\n\t\treturn strconv.FormatBool(x), nil\n\n\tcase string:\n\t\treturn \"'\" + strings.Replace(x, \"'\", \"''\", -1) + \"'\", nil\n\n\tcase []byte:\n\t\tif x == nil {\n\t\t\treturn \"NULL\", nil\n\t\t}\n\t\treturn \"X'\" + hex.EncodeToString(x) + \"'\", nil\n\n\tcase trinoDate:\n\t\treturn fmt.Sprintf(\"DATE '%04d-%02d-%02d'\", x.year, x.month, x.day), nil\n\tcase trinoTime:\n\t\treturn fmt.Sprintf(\"TIME '%02d:%02d:%02d.%09d'\", x.hour, x.minute, x.second, x.nanosecond), nil\n\tcase trinoTimeTz:\n\t\treturn \"TIME \" + time.Time(x).Format(\"'15:04:05.999999999 Z07:00'\"), nil\n\tcase trinoTimestamp:\n\t\treturn \"TIMESTAMP \" + time.Time(x).Format(\"'2006-01-02 15:04:05.999999999'\"), nil\n\tcase time.Time:\n\t\treturn \"TIMESTAMP \" + time.Time(x).Format(\"'2006-01-02 15:04:05.999999999 Z07:00'\"), nil\n\n\tcase time.Duration:\n\t\treturn serialDuration(x)\n\n\t\t// TODO - json.RawMesssage should probably be matched to 'JSON' in Trino\n\tcase json.RawMessage:\n\t\treturn \"\", UnsupportedArgError{\"json.RawMessage\"}\n\t}\n\n\tif reflect.TypeOf(v).Kind() == reflect.Slice {\n\t\tx := reflect.ValueOf(v)\n\t\tif x.IsNil() {\n\t\t\treturn \"\", UnsupportedArgError{\"[]<nil>\"}\n\t\t}\n\n\t\tslice := make([]interface{}, x.Len())\n\n\t\tfor i := 0; i < x.Len(); i++ {\n\t\t\tslice[i] = x.Index(i).Interface()\n\t\t}\n\n\t\treturn serialSlice(slice)\n\t}\n\n\tif reflect.TypeOf(v).Kind() == reflect.Map {\n\t\t// are Trino MAPs indifferent to order? Golang maps are, if Trino aren't then the two types can't be compatible\n\t\treturn \"\", UnsupportedArgError{\"map\"}\n\t}\n\n\t// TODO - consider the remaining types in https://trino.io/docs/current/language/types.html (Row, IP, ...)\n\n\treturn \"\", UnsupportedArgError{fmt.Sprintf(\"%T\", v)}\n}\n\nfunc serialSlice(v []interface{}) (string, error) {\n\tss := make([]string, len(v))\n\n\tfor i, x := range v {\n\t\ts, err := Serial(x)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tss[i] = s\n\t}\n\n\treturn \"ARRAY[\" + strings.Join(ss, \", \") + \"]\", nil\n}\n\nconst (\n\t// For seconds with milliseconds there is a maximum length of 10 digits\n\t// or 11 characters with the dot and 12 characters with the minus sign and dot\n\tmaxIntervalStrLenWithDot = 11 // 123456789.1 and 12345678.91 are valid\n)\n\nfunc serialDuration(dur time.Duration) (string, error) {\n\tswitch {\n\tcase dur%time.Hour == 0:\n\t\treturn serialHoursInterval(dur), nil\n\tcase dur%time.Minute == 0:\n\t\treturn serialMinutesInterval(dur), nil\n\tcase dur%time.Second == 0:\n\t\treturn serialSecondsInterval(dur)\n\tcase dur%time.Millisecond == 0:\n\t\treturn serialMillisecondsInterval(dur)\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"trino: duration %v is not a multiple of hours, minutes, seconds or milliseconds\", dur)\n\t}\n}\n\nfunc serialHoursInterval(dur time.Duration) string {\n\treturn \"INTERVAL '\" + strconv.Itoa(int(dur/time.Hour)) + \"' HOUR\"\n}\n\nfunc serialMinutesInterval(dur time.Duration) string {\n\treturn \"INTERVAL '\" + strconv.Itoa(int(dur/time.Minute)) + \"' MINUTE\"\n}\n\nfunc serialSecondsInterval(dur time.Duration) (string, error) {\n\tseconds := int64(dur / time.Second)\n\tif seconds <= math.MinInt32 || seconds > math.MaxInt32 {\n\t\treturn \"\", fmt.Errorf(\"trino: duration %v is out of range for interval of seconds type\", dur)\n\t}\n\treturn \"INTERVAL '\" + strconv.FormatInt(seconds, 10) + \"' SECOND\", nil\n}\n\nfunc serialMillisecondsInterval(dur time.Duration) (string, error) {\n\tseconds := int64(dur / time.Second)\n\tmillisInSecond := dur.Abs().Milliseconds() % 1000\n\tintervalNr := strings.TrimRight(fmt.Sprintf(\"%d.%03d\", seconds, millisInSecond), \"0\")\n\tif seconds > 0 && len(intervalNr) > maxIntervalStrLenWithDot ||\n\t\tseconds < 0 && len(intervalNr) > maxIntervalStrLenWithDot+1 { // +1 for the minus sign\n\t\treturn \"\", fmt.Errorf(\"trino: duration %v is out of range for interval of seconds with millis type\", dur)\n\t}\n\treturn \"INTERVAL '\" + intervalNr + \"' SECOND\", nil\n}\n"
  },
  {
    "path": "trino/serial_test.go",
    "content": "// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage trino\n\nimport (\n\t\"math\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSerial(t *testing.T) {\n\tparis, err := time.LoadLocation(\"Europe/Paris\")\n\trequire.NoError(t, err)\n\tscenarios := []struct {\n\t\tname           string\n\t\tvalue          interface{}\n\t\texpectedError  bool\n\t\texpectedSerial string\n\t}{\n\t\t{\n\t\t\tname:           \"basic string\",\n\t\t\tvalue:          \"hello world\",\n\t\t\texpectedSerial: `'hello world'`,\n\t\t},\n\t\t{\n\t\t\tname:           \"single quoted string\",\n\t\t\tvalue:          \"hello world's\",\n\t\t\texpectedSerial: `'hello world''s'`,\n\t\t},\n\t\t{\n\t\t\tname:           \"double quoted string\",\n\t\t\tvalue:          `hello \"world\"`,\n\t\t\texpectedSerial: `'hello \"world\"'`,\n\t\t},\n\t\t{\n\t\t\tname:           \"basic binary\",\n\t\t\tvalue:          []byte{0x01, 0x02, 0x03},\n\t\t\texpectedSerial: `X'010203'`,\n\t\t},\n\t\t{\n\t\t\tname:           \"empty binary\",\n\t\t\tvalue:          []byte{},\n\t\t\texpectedSerial: `X''`,\n\t\t},\n\t\t{\n\t\t\tname:           \"nil binary\",\n\t\t\tvalue:          []byte(nil),\n\t\t\texpectedSerial: `NULL`,\n\t\t},\n\t\t{\n\t\t\tname:           \"int8\",\n\t\t\tvalue:          int8(100),\n\t\t\texpectedSerial: \"100\",\n\t\t},\n\t\t{\n\t\t\tname:           \"int16\",\n\t\t\tvalue:          int16(100),\n\t\t\texpectedSerial: \"100\",\n\t\t},\n\t\t{\n\t\t\tname:           \"int32\",\n\t\t\tvalue:          int32(100),\n\t\t\texpectedSerial: \"100\",\n\t\t},\n\t\t{\n\t\t\tname:           \"int\",\n\t\t\tvalue:          int(100),\n\t\t\texpectedSerial: \"100\",\n\t\t},\n\t\t{\n\t\t\tname:           \"int64\",\n\t\t\tvalue:          int64(100),\n\t\t\texpectedSerial: \"100\",\n\t\t},\n\t\t{\n\t\t\tname:          \"uint8\",\n\t\t\tvalue:         uint8(100),\n\t\t\texpectedError: true,\n\t\t},\n\t\t{\n\t\t\tname:           \"uint16\",\n\t\t\tvalue:          uint16(100),\n\t\t\texpectedSerial: \"100\",\n\t\t},\n\t\t{\n\t\t\tname:           \"uint32\",\n\t\t\tvalue:          uint32(100),\n\t\t\texpectedSerial: \"100\",\n\t\t},\n\t\t{\n\t\t\tname:           \"uint\",\n\t\t\tvalue:          uint(100),\n\t\t\texpectedSerial: \"100\",\n\t\t},\n\t\t{\n\t\t\tname:           \"uint64\",\n\t\t\tvalue:          uint64(100),\n\t\t\texpectedSerial: \"100\",\n\t\t},\n\t\t{\n\t\t\tname:          \"byte\",\n\t\t\tvalue:         byte('a'),\n\t\t\texpectedError: true,\n\t\t},\n\t\t{\n\t\t\tname:           \"valid Numeric\",\n\t\t\tvalue:          Numeric(\"10\"),\n\t\t\texpectedSerial: \"10\",\n\t\t},\n\t\t{\n\t\t\tname:          \"invalid Numeric\",\n\t\t\tvalue:         Numeric(\"not-a-number\"),\n\t\t\texpectedError: true,\n\t\t},\n\t\t{\n\t\t\tname:           \"bool true\",\n\t\t\tvalue:          true,\n\t\t\texpectedSerial: \"true\",\n\t\t},\n\t\t{\n\t\t\tname:           \"bool false\",\n\t\t\tvalue:          false,\n\t\t\texpectedSerial: \"false\",\n\t\t},\n\t\t{\n\t\t\tname:           \"date\",\n\t\t\tvalue:          Date(2017, 7, 10),\n\t\t\texpectedSerial: \"DATE '2017-07-10'\",\n\t\t},\n\t\t{\n\t\t\tname:           \"time without timezone\",\n\t\t\tvalue:          Time(11, 34, 25, 123456),\n\t\t\texpectedSerial: \"TIME '11:34:25.000123456'\",\n\t\t},\n\t\t{\n\t\t\tname:           \"time with timezone\",\n\t\t\tvalue:          TimeTz(11, 34, 25, 123456, time.FixedZone(\"test zone\", +2*3600)),\n\t\t\texpectedSerial: \"TIME '11:34:25.000123456 +02:00'\",\n\t\t},\n\t\t{\n\t\t\tname:           \"time with timezone\",\n\t\t\tvalue:          TimeTz(11, 34, 25, 123456, nil),\n\t\t\texpectedSerial: \"TIME '11:34:25.000123456 Z'\",\n\t\t},\n\t\t{\n\t\t\tname:           \"timestamp without timezone\",\n\t\t\tvalue:          Timestamp(2017, 7, 10, 11, 34, 25, 123456),\n\t\t\texpectedSerial: \"TIMESTAMP '2017-07-10 11:34:25.000123456'\",\n\t\t},\n\t\t{\n\t\t\tname:           \"timestamp with time zone in Fixed Zone\",\n\t\t\tvalue:          time.Date(2017, 7, 10, 11, 34, 25, 123456, time.FixedZone(\"test zone\", +2*3600)),\n\t\t\texpectedSerial: \"TIMESTAMP '2017-07-10 11:34:25.000123456 +02:00'\",\n\t\t},\n\t\t{\n\t\t\tname:           \"timestamp with time zone in Named Zone\",\n\t\t\tvalue:          time.Date(2017, 7, 10, 11, 34, 25, 123456, paris),\n\t\t\texpectedSerial: \"TIMESTAMP '2017-07-10 11:34:25.000123456 +02:00'\",\n\t\t},\n\t\t{\n\t\t\tname:           \"timestamp with time zone in UTC\",\n\t\t\tvalue:          time.Date(2017, 7, 10, 11, 34, 25, 123456, time.UTC),\n\t\t\texpectedSerial: \"TIMESTAMP '2017-07-10 11:34:25.000123456 Z'\",\n\t\t},\n\t\t{\n\t\t\tname:           \"duration\",\n\t\t\tvalue:          10*time.Second + 5*time.Millisecond,\n\t\t\texpectedSerial: \"INTERVAL '10.005' SECOND\",\n\t\t},\n\t\t{\n\t\t\tname:           \"duration with negative value\",\n\t\t\tvalue:          -(10*time.Second + 5*time.Millisecond),\n\t\t\texpectedSerial: \"INTERVAL '-10.005' SECOND\",\n\t\t},\n\t\t{\n\t\t\tname:           \"minute duration\",\n\t\t\tvalue:          10 * time.Minute,\n\t\t\texpectedSerial: \"INTERVAL '10' MINUTE\",\n\t\t},\n\t\t{\n\t\t\tname:           \"hour duration\",\n\t\t\tvalue:          23 * time.Hour,\n\t\t\texpectedSerial: \"INTERVAL '23' HOUR\",\n\t\t},\n\t\t{\n\t\t\tname:           \"max hour duration\",\n\t\t\tvalue:          (math.MaxInt64 / time.Hour) * time.Hour,\n\t\t\texpectedSerial: \"INTERVAL '2562047' HOUR\",\n\t\t},\n\t\t{\n\t\t\tname:           \"min hour duration\",\n\t\t\tvalue:          (math.MinInt64 / time.Hour) * time.Hour,\n\t\t\texpectedSerial: \"INTERVAL '-2562047' HOUR\",\n\t\t},\n\t\t{\n\t\t\tname:           \"max minute duration\",\n\t\t\tvalue:          (math.MaxInt64 / time.Minute) * time.Minute,\n\t\t\texpectedSerial: \"INTERVAL '153722867' MINUTE\",\n\t\t},\n\t\t{\n\t\t\tname:           \"min minute duration\",\n\t\t\tvalue:          (math.MinInt64 / time.Minute) * time.Minute,\n\t\t\texpectedSerial: \"INTERVAL '-153722867' MINUTE\",\n\t\t},\n\t\t{\n\t\t\tname:          \"too big second duration\",\n\t\t\tvalue:         (math.MaxInt64 / time.Second) * time.Second,\n\t\t\texpectedError: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"too small second duration\",\n\t\t\tvalue:         (math.MinInt64 / time.Second) * time.Second,\n\t\t\texpectedError: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"too big millisecond duration\",\n\t\t\tvalue:         time.Millisecond*912 + time.Second*12345678,\n\t\t\texpectedError: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"too small millisecond duration\",\n\t\t\tvalue:         -(time.Millisecond*910 + time.Second*123456789),\n\t\t\texpectedError: true,\n\t\t},\n\t\t{\n\t\t\tname:           \"max allowed second duration\",\n\t\t\tvalue:          math.MaxInt32 * time.Second,\n\t\t\texpectedSerial: \"INTERVAL '2147483647' SECOND\",\n\t\t},\n\t\t{\n\t\t\tname:           \"min allowed second duration\",\n\t\t\tvalue:          -math.MaxInt32 * time.Second,\n\t\t\texpectedSerial: \"INTERVAL '-2147483647' SECOND\",\n\t\t},\n\t\t{\n\t\t\tname:           \"max allowed second with milliseconds duration\",\n\t\t\tvalue:          999999999*time.Second + 900*time.Millisecond,\n\t\t\texpectedSerial: \"INTERVAL '999999999.9' SECOND\",\n\t\t},\n\t\t{\n\t\t\tname:           \"min allowed second with milliseconds duration\",\n\t\t\tvalue:          -999999999*time.Second - 900*time.Millisecond,\n\t\t\texpectedSerial: \"INTERVAL '-999999999.9' SECOND\",\n\t\t},\n\t\t{\n\t\t\tname:           \"nil\",\n\t\t\tvalue:          nil,\n\t\t\texpectedSerial: \"NULL\",\n\t\t},\n\t\t{\n\t\t\tname:          \"slice typed nil\",\n\t\t\tvalue:         []interface{}(nil),\n\t\t\texpectedError: true,\n\t\t},\n\t\t{\n\t\t\tname:           \"valid slice\",\n\t\t\tvalue:          []interface{}{1, 2},\n\t\t\texpectedSerial: \"ARRAY[1, 2]\",\n\t\t},\n\t\t{\n\t\t\tname:           \"valid empty\",\n\t\t\tvalue:          []interface{}{},\n\t\t\texpectedSerial: \"ARRAY[]\",\n\t\t},\n\t\t{\n\t\t\tname:          \"invalid slice contents\",\n\t\t\tvalue:         []interface{}{1, byte('a')},\n\t\t\texpectedError: true,\n\t\t},\n\t}\n\n\tfor i := range scenarios {\n\t\tscenario := scenarios[i]\n\n\t\tt.Run(scenario.name, func(t *testing.T) {\n\t\t\ts, err := Serial(scenario.value)\n\t\t\tif err != nil {\n\t\t\t\tif scenario.expectedError {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tif scenario.expectedError {\n\t\t\t\tt.Fatal(\"missing an expected error\")\n\t\t\t}\n\n\t\t\tif scenario.expectedSerial != s {\n\t\t\t\tt.Fatalf(\"mismatched serial, got %q expected %q\", s, scenario.expectedSerial)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "trino/trino.go",
    "content": "// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// This file contains code that was borrowed from prestgo, mainly some\n// data type definitions.\n//\n// See https://github.com/avct/prestgo for copyright information.\n//\n// The MIT License (MIT)\n//\n// Copyright (c) 2015 Avocet Systems Ltd.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n// Package trino provides a database/sql driver for Trino.\n//\n// The driver should be used via the database/sql package:\n//\n//\timport \"database/sql\"\n//\timport _ \"github.com/trinodb/trino-go-client/trino\"\n//\n//\tdsn := \"http://user@localhost:8080?catalog=default&schema=test\"\n//\tdb, err := sql.Open(\"trino\", dsn)\npackage trino\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"database/sql\"\n\t\"database/sql/driver\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"reflect\"\n\t\"slices\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\t\"unicode\"\n\n\t\"github.com/jcmturner/gokrb5/v8/client\"\n\t\"github.com/jcmturner/gokrb5/v8/config\"\n\t\"github.com/jcmturner/gokrb5/v8/keytab\"\n\t\"github.com/jcmturner/gokrb5/v8/spnego\"\n\t\"github.com/klauspost/compress/zstd\"\n\t\"github.com/pierrec/lz4\"\n)\n\nfunc init() {\n\tsql.Register(\"trino\", &Driver{})\n}\n\nvar (\n\t// DefaultQueryTimeout is the default timeout for queries executed without a context.\n\tDefaultQueryTimeout = 10 * time.Hour\n\n\t// DefaultCancelQueryTimeout is the timeout for the request to cancel queries in Trino.\n\tDefaultCancelQueryTimeout = 30 * time.Second\n\n\t// ErrOperationNotSupported indicates that a database operation is not supported.\n\tErrOperationNotSupported = errors.New(\"trino: operation not supported\")\n\n\t// ErrQueryCancelled indicates that a query has been cancelled.\n\tErrQueryCancelled = errors.New(\"trino: query cancelled\")\n\n\t// ErrUnsupportedHeader indicates that the server response contains an unsupported header.\n\tErrUnsupportedHeader = errors.New(\"trino: server response contains an unsupported header\")\n\n\t// ErrInvalidResponseType indicates that the server returned an invalid type definition.\n\tErrInvalidResponseType = errors.New(\"trino: server response contains an invalid type\")\n\n\t// ErrInvalidProgressCallbackHeader indicates that server did not get valid headers for progress callback\n\tErrInvalidProgressCallbackHeader = errors.New(\"trino: both \" + trinoProgressCallbackParam + \" and \" + trinoProgressCallbackPeriodParam + \" must be set when using progress callback\")\n)\n\nconst (\n\ttrinoHeaderPrefix = `X-Trino-`\n\n\tpreparedStatementHeader = trinoHeaderPrefix + \"Prepared-Statement\"\n\tpreparedStatementName   = \"_trino_go\"\n\n\ttrinoUserHeader            = trinoHeaderPrefix + `User`\n\ttrinoSourceHeader          = trinoHeaderPrefix + `Source`\n\ttrinoCatalogHeader         = trinoHeaderPrefix + `Catalog`\n\ttrinoSchemaHeader          = trinoHeaderPrefix + `Schema`\n\ttrinoSessionHeader         = trinoHeaderPrefix + `Session`\n\ttrinoSetCatalogHeader      = trinoHeaderPrefix + `Set-Catalog`\n\ttrinoSetSchemaHeader       = trinoHeaderPrefix + `Set-Schema`\n\ttrinoSetPathHeader         = trinoHeaderPrefix + `Set-Path`\n\ttrinoSetSessionHeader      = trinoHeaderPrefix + `Set-Session`\n\ttrinoClearSessionHeader    = trinoHeaderPrefix + `Clear-Session`\n\ttrinoSetRoleHeader         = trinoHeaderPrefix + `Set-Role`\n\ttrinoRoleHeader            = trinoHeaderPrefix + `Role`\n\ttrinoExtraCredentialHeader = trinoHeaderPrefix + `Extra-Credential`\n\n\ttrinoProgressCallbackParam       = trinoHeaderPrefix + `Progress-Callback`\n\ttrinoProgressCallbackPeriodParam = trinoHeaderPrefix + `Progress-Callback-Period`\n\n\ttrinoAddedPrepareHeader       = trinoHeaderPrefix + `Added-Prepare`\n\ttrinoDeallocatedPrepareHeader = trinoHeaderPrefix + `Deallocated-Prepare`\n\ttrinoTagsHeader               = trinoHeaderPrefix + `Client-Tags`\n\n\ttrinoQueryDataEncodingHeader = trinoHeaderPrefix + `Query-Data-Encoding`\n\ttrinoEncoding                = \"encoding\"\n\n\ttrinoSpoolingWorkerCount    = `spooling_worker_count`\n\ttrinoMaxOutOfOrdersSegments = `max_out_of_order_segments`\n\n\tauthorizationHeader = \"Authorization\"\n\n\tkerberosEnabledConfig            = \"KerberosEnabled\"\n\tkerberosKeytabPathConfig         = \"KerberosKeytabPath\"\n\tkerberosPrincipalConfig          = \"KerberosPrincipal\"\n\tkerberosRealmConfig              = \"KerberosRealm\"\n\tkerberosConfigPathConfig         = \"KerberosConfigPath\"\n\tkerberosRemoteServiceNameConfig  = \"KerberosRemoteServiceName\"\n\tsslCertPathConfig                = \"SSLCertPath\"\n\tsslCertConfig                    = \"SSLCert\"\n\taccessTokenConfig                = \"accessToken\"\n\texplicitPrepareConfig            = \"explicitPrepare\"\n\tforwardAuthorizationHeaderConfig = \"forwardAuthorizationHeader\"\n\n\tmapKeySeparator   = \":\"\n\tmapEntrySeparator = \";\"\n\tcommaSeparator    = \",\"\n\n\tdefaultallowedOutOfOrder       = 10\n\tdefaultSpoolingDownloadWorkers = 5\n\tdefaulttrinoEncoding           = \"json\"\n\tdefaultSourceName              = \"trino-go-client\"\n\tdefaultKerberosServiceName     = \"trino\"\n)\n\nvar (\n\tresponseToRequestHeaderMap = map[string]string{\n\t\ttrinoSetSchemaHeader:  trinoSchemaHeader,\n\t\ttrinoSetCatalogHeader: trinoCatalogHeader,\n\t\ttrinoSetRoleHeader:    trinoRoleHeader,\n\t}\n\tunsupportedResponseHeaders = []string{\n\t\ttrinoSetPathHeader,\n\t}\n)\n\ntype Driver struct{}\n\nfunc (d *Driver) Open(name string) (driver.Conn, error) {\n\treturn newConn(name)\n}\n\nvar _ driver.Driver = &Driver{}\n\n// Config is a configuration that can be encoded to a DSN string.\ntype Config struct {\n\tServerURI                  string            // URI of the Trino server, e.g. http://user@localhost:8080\n\tSource                     string            // Source of the connection (optional)\n\tCatalog                    string            // Catalog (optional)\n\tSchema                     string            // Schema (optional)\n\tSessionProperties          map[string]string // Session properties (optional)\n\tExtraCredentials           map[string]string // Extra credentials (optional)\n\tClientTags                 []string          // A comma-separated list of “tag” strings, used to identify Trino resource groups (optional)\n\tCustomClientName           string            // Custom client name (optional)\n\tKerberosEnabled            bool              // KerberosEnabled (optional, default is false)\n\tKerberosKeytabPath         string            // Kerberos Keytab Path (optional)\n\tKerberosPrincipal          string            // Kerberos Principal used to authenticate to KDC (optional)\n\tKerberosRemoteServiceName  string            // Trino coordinator Kerberos service name (optional)\n\tKerberosRealm              string            // The Kerberos Realm (optional)\n\tKerberosConfigPath         string            // The krb5 config path (optional)\n\tSSLCertPath                string            // The SSL cert path for TLS verification (optional)\n\tSSLCert                    string            // The SSL cert for TLS verification (optional)\n\tAccessToken                string            // An access token (JWT) for authentication (optional)\n\tDisableExplicitPrepare     bool              // Disable the use of explicit prepared statements (optional, default is false)\n\tForwardAuthorizationHeader bool              // Allow forwarding the `accessToken` named query parameter in the authorization header, overwriting the `AccessToken` option, if set (optional)\n\tQueryTimeout               *time.Duration    // Configurable timeout for query (optional)\n\tRoles                      map[string]string // Roles (optional)\n}\n\nfunc (c *Config) applyDefaults() {\n\tif c.Source == \"\" {\n\t\tc.Source = defaultSourceName\n\t}\n\n\tif c.KerberosRemoteServiceName == \"\" && c.KerberosEnabled {\n\t\tc.KerberosRemoteServiceName = defaultKerberosServiceName\n\t}\n}\n\nfunc ParseDSN(dsn string) (*Config, error) {\n\tserverURL, err := url.Parse(dsn)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid DSN: %w\", err)\n\t}\n\n\tquery := serverURL.Query()\n\tconfig := &Config{}\n\n\tserverURI := serverURL.Scheme + \"://\"\n\tif serverURL.User != nil {\n\t\tserverURI += serverURL.User.String() + \"@\"\n\t}\n\n\tserverURI += serverURL.Host\n\n\tconfig.ServerURI = serverURI\n\tconfig.Source = query.Get(\"source\")\n\n\tconfig.Catalog = query.Get(\"catalog\")\n\tconfig.Schema = query.Get(\"schema\")\n\n\tif sessionProps := query.Get(\"session_properties\"); sessionProps != \"\" {\n\t\tvar err error\n\t\tconfig.SessionProperties, err = parseMapParameter(sessionProps, \"session property\", mapEntrySeparator, mapKeySeparator)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif extraCreds := query.Get(\"extra_credentials\"); extraCreds != \"\" {\n\t\tvar err error\n\t\tconfig.ExtraCredentials, err = parseMapParameter(extraCreds, \"extra credential\", mapEntrySeparator, mapKeySeparator)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif roles := query.Get(\"roles\"); roles != \"\" {\n\t\tvar err error\n\t\tconfig.Roles, err = parseMapParameter(roles, \"role\", mapEntrySeparator, mapKeySeparator)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif clientTags := query.Get(\"clientTags\"); clientTags != \"\" {\n\t\tconfig.ClientTags = strings.Split(clientTags, commaSeparator)\n\t}\n\n\tconfig.CustomClientName = query.Get(\"custom_client\")\n\tconfig.AccessToken = query.Get(accessTokenConfig)\n\n\tif explicitPrepare := query.Get(explicitPrepareConfig); explicitPrepare != \"\" {\n\t\texplicitPrepareValue, err := strconv.ParseBool(explicitPrepare)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid boolean for %s: %q\", explicitPrepareConfig, explicitPrepare)\n\t\t}\n\t\tconfig.DisableExplicitPrepare = !explicitPrepareValue\n\t}\n\n\tif forwardAuth := query.Get(forwardAuthorizationHeaderConfig); forwardAuth != \"\" {\n\t\tforwardAuthValue, err := strconv.ParseBool(forwardAuth)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid boolean for %s: %q\", forwardAuthorizationHeaderConfig, forwardAuth)\n\t\t}\n\t\tconfig.ForwardAuthorizationHeader = forwardAuthValue\n\t}\n\n\tif queryTimeoutStr := query.Get(\"query_timeout\"); queryTimeoutStr != \"\" {\n\t\tqueryTimeout, err := time.ParseDuration(queryTimeoutStr)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"trino: invalid timeout for query_timeout: %q\", queryTimeoutStr)\n\t\t}\n\t\tconfig.QueryTimeout = &queryTimeout\n\t}\n\n\tif kerberosParam := query.Get(kerberosEnabledConfig); kerberosParam != \"\" {\n\t\tenabled, err := strconv.ParseBool(kerberosParam)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid boolean for %s: %q\", kerberosEnabledConfig, kerberosParam)\n\t\t}\n\t\tconfig.KerberosEnabled = enabled\n\t}\n\n\tif kp := query.Get(kerberosKeytabPathConfig); kp != \"\" {\n\t\tconfig.KerberosKeytabPath = kp\n\t}\n\n\tif p := query.Get(kerberosPrincipalConfig); p != \"\" {\n\t\tconfig.KerberosPrincipal = p\n\t}\n\n\tif r := query.Get(kerberosRealmConfig); r != \"\" {\n\t\tconfig.KerberosRealm = r\n\t}\n\n\tif kp := query.Get(kerberosConfigPathConfig); kp != \"\" {\n\t\tconfig.KerberosConfigPath = kp\n\t}\n\n\tif rsn := query.Get(kerberosRemoteServiceNameConfig); rsn != \"\" {\n\t\tconfig.KerberosRemoteServiceName = rsn\n\t}\n\n\tif sslCertPath := query.Get(sslCertPathConfig); sslCertPath != \"\" {\n\t\tconfig.SSLCertPath = sslCertPath\n\t}\n\n\tif sslCert := query.Get(sslCertConfig); sslCert != \"\" {\n\t\tconfig.SSLCert = sslCert\n\t}\n\n\tconfig.applyDefaults()\n\treturn config, nil\n}\n\nfunc parseMapParameter(value, paramName, entrySeparator, keyValueSeparator string) (map[string]string, error) {\n\tresult := make(map[string]string)\n\tfor _, entry := range strings.Split(value, entrySeparator) {\n\t\tparts := strings.SplitN(entry, keyValueSeparator, 2)\n\t\tif len(parts) != 2 {\n\t\t\treturn nil, fmt.Errorf(\"invalid %s entry: %q\", paramName, entry)\n\t\t}\n\t\tresult[parts[0]] = parts[1]\n\t}\n\treturn result, nil\n}\n\nfunc (c *Config) FormatDSN() (string, error) {\n\tc.applyDefaults()\n\n\tserverURL, err := url.Parse(c.ServerURI)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tvar sessionkv []string\n\tif c.SessionProperties != nil {\n\t\tfor k, v := range c.SessionProperties {\n\t\t\tsessionkv = append(sessionkv, k+mapKeySeparator+v)\n\t\t}\n\t}\n\tvar credkv []string\n\tif c.ExtraCredentials != nil {\n\t\tfor k, v := range c.ExtraCredentials {\n\t\t\tcredkv = append(credkv, k+mapKeySeparator+v)\n\t\t}\n\t}\n\n\tvar roles []string\n\tif c.Roles != nil {\n\t\tfor k, v := range c.Roles {\n\t\t\troles = append(roles, fmt.Sprintf(\"%s:%s\", k, v))\n\t\t}\n\t}\n\n\tquery := make(url.Values)\n\tquery.Add(\"source\", c.Source)\n\n\tif c.ForwardAuthorizationHeader {\n\t\tquery.Add(forwardAuthorizationHeaderConfig, \"true\")\n\t}\n\n\tisSSL := serverURL.Scheme == \"https\"\n\n\tif c.DisableExplicitPrepare {\n\t\tquery.Add(explicitPrepareConfig, \"false\")\n\t}\n\n\tif c.CustomClientName != \"\" {\n\t\tif c.SSLCert != \"\" || c.SSLCertPath != \"\" {\n\t\t\treturn \"\", fmt.Errorf(\"trino: client configuration error, a custom client cannot be specific together with a custom SSL certificate\")\n\t\t}\n\t}\n\tif c.SSLCertPath != \"\" {\n\t\tif !isSSL {\n\t\t\treturn \"\", fmt.Errorf(\"trino: client configuration error, SSL must be enabled to specify a custom SSL certificate file\")\n\t\t}\n\t\tif c.SSLCert != \"\" {\n\t\t\treturn \"\", fmt.Errorf(\"trino: client configuration error, a custom SSL certificate file cannot be specified together with a certificate string\")\n\t\t}\n\t\tquery.Add(sslCertPathConfig, c.SSLCertPath)\n\t}\n\n\tif c.SSLCert != \"\" {\n\t\tif !isSSL {\n\t\t\treturn \"\", fmt.Errorf(\"trino: client configuration error, SSL must be enabled to specify a custom SSL certificate\")\n\t\t}\n\t\tif c.SSLCertPath != \"\" {\n\t\t\treturn \"\", fmt.Errorf(\"trino: client configuration error, a custom SSL certificate string cannot be specified together with a certificate file\")\n\t\t}\n\t\tquery.Add(sslCertConfig, c.SSLCert)\n\t}\n\n\tif c.KerberosEnabled {\n\t\tif !isSSL {\n\t\t\treturn \"\", fmt.Errorf(\"trino: client configuration error, SSL must be enabled for secure env\")\n\t\t}\n\t\tquery.Add(kerberosEnabledConfig, \"true\")\n\t\tquery.Add(kerberosKeytabPathConfig, c.KerberosKeytabPath)\n\t\tquery.Add(kerberosPrincipalConfig, c.KerberosPrincipal)\n\t\tquery.Add(kerberosRealmConfig, c.KerberosRealm)\n\t\tquery.Add(kerberosConfigPathConfig, c.KerberosConfigPath)\n\t\tquery.Add(kerberosRemoteServiceNameConfig, c.KerberosRemoteServiceName)\n\t}\n\n\t// ensure consistent order of items\n\tsort.Strings(sessionkv)\n\tsort.Strings(credkv)\n\tsort.Strings(roles)\n\n\tif c.QueryTimeout != nil {\n\t\tquery.Add(\"query_timeout\", c.QueryTimeout.String())\n\t}\n\n\tfor k, v := range map[string]string{\n\t\t\"catalog\":            c.Catalog,\n\t\t\"clientTags\":         strings.Join(c.ClientTags, commaSeparator),\n\t\t\"schema\":             c.Schema,\n\t\t\"session_properties\": strings.Join(sessionkv, mapEntrySeparator),\n\t\t\"extra_credentials\":  strings.Join(credkv, mapEntrySeparator),\n\t\t\"custom_client\":      c.CustomClientName,\n\t\taccessTokenConfig:    c.AccessToken,\n\t\t\"roles\":              strings.Join(roles, mapEntrySeparator),\n\t} {\n\t\tif v != \"\" {\n\t\t\tquery[k] = []string{v}\n\t\t}\n\t}\n\tserverURL.RawQuery = query.Encode()\n\treturn serverURL.String(), nil\n}\n\n// Conn is a Trino connection.\ntype Conn struct {\n\tbaseURL                    string\n\tauth                       *url.Userinfo\n\thttpClient                 http.Client\n\thttpHeaders                http.Header\n\tkerberosEnabled            bool\n\tkerberosClient             *client.Client\n\tkerberosRemoteServiceName  string\n\tprogressUpdater            ProgressUpdater\n\tprogressUpdaterPeriod      queryProgressCallbackPeriod\n\tuseExplicitPrepare         bool\n\tforwardAuthorizationHeader bool\n\tqueryTimeout               *time.Duration\n}\n\nvar (\n\t_ driver.Conn               = &Conn{}\n\t_ driver.ConnPrepareContext = &Conn{}\n)\n\n// formatRolesFromMap formats roles from a map into the Trino header format\nfunc formatRolesFromMap(rolesMap map[string]string) string {\n\tvar formattedRoles []string\n\tfor catalog, role := range rolesMap {\n\t\tformattedRoles = append(formattedRoles, formatRoleEntry(catalog, role))\n\t}\n\tsort.Strings(formattedRoles)\n\treturn strings.Join(formattedRoles, commaSeparator)\n}\n\n// formatRoleEntry formats a single catalog role entry into Trino header format\nfunc formatRoleEntry(catalog, role string) string {\n\tif role == \"ALL\" || role == \"NONE\" {\n\t\treturn fmt.Sprintf(\"%s=%s\", catalog, role)\n\t}\n\treturn fmt.Sprintf(\"%s=ROLE{%s}\", catalog, role)\n}\n\n// formatHeaderValue converts a named argument value to a string suitable for HTTP headers.\nfunc formatHeaderValue(headerName string, value interface{}) (string, error) {\n\tif headerName == trinoRoleHeader {\n\t\trolesMap, ok := value.(map[string]string)\n\t\tif !ok {\n\t\t\treturn \"\", fmt.Errorf(\"%s must be a map[string]string, got %T\", trinoRoleHeader, value)\n\t\t}\n\t\treturn formatRolesFromMap(rolesMap), nil\n\t}\n\n\theaderValue, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"%s must be a string, got %T\", headerName, value)\n\t}\n\treturn headerValue, nil\n}\n\nfunc newConn(dsn string) (*Conn, error) {\n\tconf, err := ParseDSN(dsn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar kerberosClient *client.Client\n\n\tif conf.KerberosEnabled {\n\t\tkt, err := keytab.Load(conf.KerberosKeytabPath)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"trino: Error loading Keytab: %w\", err)\n\t\t}\n\t\tconfKerb, err := config.Load(conf.KerberosConfigPath)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"trino: Error loading krb config: %w\", err)\n\t\t}\n\n\t\tkerberosClient = client.NewWithKeytab(conf.KerberosPrincipal, conf.KerberosRealm, kt, confKerb)\n\t\tloginErr := kerberosClient.Login()\n\t\tif loginErr != nil {\n\t\t\treturn nil, fmt.Errorf(\"trino: Error login to KDC: %v\", loginErr)\n\t\t}\n\t}\n\n\tserverURL, err := url.Parse(conf.ServerURI)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"trino: invalid server URL: %w\", err)\n\t}\n\n\tvar httpClient = http.DefaultClient\n\tif clientKey := conf.CustomClientName; clientKey != \"\" {\n\t\thttpClient = getCustomClient(clientKey)\n\t\tif httpClient == nil {\n\t\t\treturn nil, fmt.Errorf(\"trino: custom client not registered: %q\", clientKey)\n\t\t}\n\t} else if serverURL.Scheme == \"https\" {\n\n\t\tcert := []byte(conf.SSLCert)\n\n\t\tif certPath := conf.SSLCertPath; certPath != \"\" {\n\t\t\tcert, err = os.ReadFile(certPath)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"trino: Error loading SSL Cert File: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\tif len(cert) != 0 {\n\t\t\tcertPool := x509.NewCertPool()\n\t\t\tcertPool.AppendCertsFromPEM(cert)\n\n\t\t\thttpClient = &http.Client{\n\t\t\t\tTransport: &http.Transport{\n\t\t\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\t\t\tRootCAs: certPool,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\t}\n\n\tc := &Conn{\n\t\tbaseURL:                    serverURL.Scheme + \"://\" + serverURL.Host,\n\t\thttpClient:                 *httpClient,\n\t\thttpHeaders:                make(http.Header),\n\t\tkerberosClient:             kerberosClient,\n\t\tkerberosEnabled:            conf.KerberosEnabled,\n\t\tkerberosRemoteServiceName:  conf.KerberosRemoteServiceName,\n\t\tuseExplicitPrepare:         !conf.DisableExplicitPrepare,\n\t\tforwardAuthorizationHeader: conf.ForwardAuthorizationHeader,\n\t\tqueryTimeout:               conf.QueryTimeout,\n\t}\n\n\tvar user string\n\tif serverURL.User != nil {\n\t\tuser = serverURL.User.Username()\n\t\tpass, _ := serverURL.User.Password()\n\t\tif pass != \"\" && serverURL.Scheme == \"https\" {\n\t\t\tc.auth = serverURL.User\n\t\t}\n\t}\n\n\tif tags := conf.ClientTags; tags != nil {\n\t\tc.httpHeaders.Add(trinoTagsHeader, strings.Join(tags, commaSeparator))\n\t}\n\n\tif conf.Roles != nil {\n\t\trolesHeader := formatRolesFromMap(conf.Roles)\n\t\tif rolesHeader != \"\" {\n\t\t\tc.httpHeaders.Add(trinoRoleHeader, rolesHeader)\n\t\t}\n\t}\n\n\tfor k, v := range map[string]string{\n\t\ttrinoUserHeader:     user,\n\t\ttrinoSourceHeader:   conf.Source,\n\t\ttrinoCatalogHeader:  conf.Catalog,\n\t\ttrinoSchemaHeader:   conf.Schema,\n\t\tauthorizationHeader: getAuthorization(conf.AccessToken),\n\t} {\n\t\tif v != \"\" {\n\t\t\tc.httpHeaders.Add(k, v)\n\t\t}\n\t}\n\n\tif conf.ExtraCredentials != nil {\n\t\tc.httpHeaders[trinoExtraCredentialHeader], err = decodeMapHeader(\"extra_credentials\", conf.ExtraCredentials)\n\t\tif err != nil {\n\t\t\treturn c, err\n\t\t}\n\t}\n\n\tif conf.SessionProperties != nil {\n\t\tc.httpHeaders[trinoSessionHeader], err = decodeMapHeader(\"session_properties\", conf.SessionProperties)\n\t\tif err != nil {\n\t\t\treturn c, err\n\t\t}\n\t}\n\n\treturn c, nil\n}\n\nfunc decodeMapHeader(name string, m map[string]string) ([]string, error) {\n\tresult := make([]string, 0, len(m))\n\tfor key, value := range m {\n\t\tif len(key) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"trino: %s key is empty\", name)\n\t\t}\n\t\tif len(value) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"trino: %s value is empty\", name)\n\t\t}\n\t\tif !isASCII(key) {\n\t\t\treturn nil, fmt.Errorf(\"trino: %s key '%s' contains spaces or is not printable ASCII\", name, key)\n\t\t}\n\t\tif !isASCII(value) {\n\t\t\treturn nil, fmt.Errorf(\"trino: %s value for key '%s' contains spaces or is not printable ASCII\", name, key)\n\t\t}\n\t\tresult = append(result, key+\"=\"+url.QueryEscape(value))\n\t}\n\treturn result, nil\n}\n\nfunc isASCII(s string) bool {\n\tfor i := 0; i < len(s); i++ {\n\t\tif s[i] < '\\u0021' || s[i] > '\\u007E' {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc getAuthorization(token string) string {\n\tif token == \"\" {\n\t\treturn \"\"\n\t}\n\treturn fmt.Sprintf(\"Bearer %s\", token)\n}\n\n// registry for custom http clients\nvar customClientRegistry = struct {\n\tsync.RWMutex\n\tIndex map[string]http.Client\n}{\n\tIndex: make(map[string]http.Client),\n}\n\n// RegisterCustomClient associates a client to a key in the driver's registry.\n//\n// Register your custom client in the driver, then refer to it by name in the DSN, on the call to sql.Open:\n//\n//\tfoobarClient := &http.Client{\n//\t\tTransport: &http.Transport{\n//\t\t\tProxy: http.ProxyFromEnvironment,\n//\t\t\tDialContext: (&net.Dialer{\n//\t\t\t\tTimeout:   30 * time.Second,\n//\t\t\t\tKeepAlive: 30 * time.Second,\n//\t\t\t\tDualStack: true,\n//\t\t\t}).DialContext,\n//\t\t\tMaxIdleConns:          100,\n//\t\t\tIdleConnTimeout:       90 * time.Second,\n//\t\t\tTLSHandshakeTimeout:   10 * time.Second,\n//\t\t\tExpectContinueTimeout: 1 * time.Second,\n//\t\t\tTLSClientConfig:       &tls.Config{\n//\t\t\t// your config here...\n//\t\t\t},\n//\t\t},\n//\t}\n//\ttrino.RegisterCustomClient(\"foobar\", foobarClient)\n//\tdb, err := sql.Open(\"trino\", \"https://user@localhost:8080?custom_client=foobar\")\nfunc RegisterCustomClient(key string, client *http.Client) error {\n\tif _, err := strconv.ParseBool(key); err == nil {\n\t\treturn fmt.Errorf(\"trino: custom client key %q is reserved\", key)\n\t}\n\tcustomClientRegistry.Lock()\n\tcustomClientRegistry.Index[key] = *client\n\tcustomClientRegistry.Unlock()\n\treturn nil\n}\n\n// DeregisterCustomClient removes the client associated to the key.\nfunc DeregisterCustomClient(key string) {\n\tcustomClientRegistry.Lock()\n\tdelete(customClientRegistry.Index, key)\n\tcustomClientRegistry.Unlock()\n}\n\nfunc getCustomClient(key string) *http.Client {\n\tcustomClientRegistry.RLock()\n\tdefer customClientRegistry.RUnlock()\n\tif client, ok := customClientRegistry.Index[key]; ok {\n\t\treturn &client\n\t}\n\treturn nil\n}\n\n// Begin implements the driver.Conn interface.\nfunc (c *Conn) Begin() (driver.Tx, error) {\n\treturn nil, ErrOperationNotSupported\n}\n\n// Prepare implements the driver.Conn interface.\nfunc (c *Conn) Prepare(query string) (driver.Stmt, error) {\n\treturn nil, driver.ErrSkip\n}\n\n// PrepareContext implements the driver.ConnPrepareContext interface.\nfunc (c *Conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {\n\treturn &driverStmt{conn: c, query: query}, nil\n}\n\n// Close implements the driver.Conn interface.\nfunc (c *Conn) Close() error {\n\treturn nil\n}\n\nfunc (c *Conn) newRequest(ctx context.Context, method, url string, body io.Reader, hs http.Header) (*http.Request, error) {\n\treq, err := http.NewRequestWithContext(ctx, method, url, body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"trino: %w\", err)\n\t}\n\n\tif c.kerberosEnabled {\n\t\tremoteServiceName := \"trino\"\n\t\tif c.kerberosRemoteServiceName != \"\" {\n\t\t\tremoteServiceName = c.kerberosRemoteServiceName\n\t\t}\n\t\terr = spnego.SetSPNEGOHeader(c.kerberosClient, req, remoteServiceName+\"/\"+req.URL.Hostname())\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error setting client SPNEGO header: %w\", err)\n\t\t}\n\t}\n\n\tfor k, v := range c.httpHeaders {\n\t\treq.Header[k] = v\n\t}\n\tfor k, v := range hs {\n\t\treq.Header[k] = v\n\t}\n\n\tif c.auth != nil {\n\t\tpass, _ := c.auth.Password()\n\t\treq.SetBasicAuth(c.auth.Username(), pass)\n\t}\n\treturn req, nil\n}\n\nfunc (c *Conn) roundTrip(ctx context.Context, req *http.Request) (*http.Response, error) {\n\tdelay := 100 * time.Millisecond\n\tconst maxDelayBetweenRequests = float64(15 * time.Second)\n\ttimer := time.NewTimer(0)\n\tdefer timer.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil, ctx.Err()\n\t\tcase <-timer.C:\n\t\t\tresp, err := c.httpClient.Do(req)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, &ErrQueryFailed{Reason: err}\n\t\t\t}\n\t\t\tswitch resp.StatusCode {\n\t\t\tcase http.StatusOK:\n\t\t\t\tfor src, dst := range responseToRequestHeaderMap {\n\t\t\t\t\tif v := resp.Header.Get(src); v != \"\" {\n\t\t\t\t\t\tc.httpHeaders.Set(dst, v)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif v := resp.Header.Get(trinoAddedPrepareHeader); v != \"\" {\n\t\t\t\t\tc.httpHeaders.Add(preparedStatementHeader, v)\n\t\t\t\t}\n\t\t\t\tif v := resp.Header.Get(trinoDeallocatedPrepareHeader); v != \"\" {\n\t\t\t\t\tvalues := c.httpHeaders.Values(preparedStatementHeader)\n\t\t\t\t\tc.httpHeaders.Del(preparedStatementHeader)\n\t\t\t\t\tfor _, v2 := range values {\n\t\t\t\t\t\tif !strings.HasPrefix(v2, v+\"=\") {\n\t\t\t\t\t\t\tc.httpHeaders.Add(preparedStatementHeader, v2)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif v := resp.Header.Get(trinoSetSessionHeader); v != \"\" {\n\t\t\t\t\tc.httpHeaders.Add(trinoSessionHeader, v)\n\t\t\t\t}\n\n\t\t\t\tif v := resp.Header.Get(trinoClearSessionHeader); v != \"\" {\n\t\t\t\t\tvalues := c.httpHeaders.Values(trinoSessionHeader)\n\t\t\t\t\tc.httpHeaders.Del(trinoSessionHeader)\n\t\t\t\t\tfor _, v2 := range values {\n\t\t\t\t\t\tif !strings.HasPrefix(v2, v+\"=\") {\n\t\t\t\t\t\t\tc.httpHeaders.Add(trinoSessionHeader, v2)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor _, name := range unsupportedResponseHeaders {\n\t\t\t\t\tif v := resp.Header.Get(name); v != \"\" {\n\t\t\t\t\t\treturn nil, ErrUnsupportedHeader\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn resp, nil\n\t\t\tcase http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:\n\t\t\t\tresp.Body.Close()\n\t\t\t\ttimer.Reset(delay)\n\t\t\t\tdelay = time.Duration(math.Min(\n\t\t\t\t\tfloat64(delay)*math.Phi,\n\t\t\t\t\tmaxDelayBetweenRequests,\n\t\t\t\t))\n\t\t\t\tcontinue\n\t\t\tdefault:\n\t\t\t\treturn nil, newErrQueryFailedFromResponse(resp)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ErrQueryFailed indicates that a query to Trino failed.\ntype ErrQueryFailed struct {\n\tStatusCode int\n\tReason     error\n}\n\n// Error implements the error interface.\nfunc (e *ErrQueryFailed) Error() string {\n\treturn fmt.Sprintf(\"trino: query failed (%d %s): %q\",\n\t\te.StatusCode, http.StatusText(e.StatusCode), e.Reason)\n}\n\n// Unwrap implements the unwrap interface.\nfunc (e *ErrQueryFailed) Unwrap() error {\n\treturn e.Reason\n}\n\nfunc newErrQueryFailedFromResponse(resp *http.Response) *ErrQueryFailed {\n\tconst maxBytes = 8 * 1024\n\tdefer resp.Body.Close()\n\tqf := &ErrQueryFailed{StatusCode: resp.StatusCode}\n\tb, err := io.ReadAll(io.LimitReader(resp.Body, maxBytes))\n\tif err != nil {\n\t\tqf.Reason = err\n\t\treturn qf\n\t}\n\treason := string(b)\n\tif resp.ContentLength > maxBytes {\n\t\treason += \"...\"\n\t}\n\tqf.Reason = errors.New(reason)\n\treturn qf\n}\n\ntype driverStmt struct {\n\tconn                          *Conn\n\tquery                         string\n\tuser                          string\n\tnextURIs                      chan string\n\thttpResponses                 chan *http.Response\n\tqueryResponses                chan queryResponse\n\tstatsCh                       chan QueryProgressInfo\n\tusingSpooledProtocol          bool\n\tspoolingMaxOutOfOrderSegments int\n\tspoolingWorkerCount           int\n\tspooledSegmentsMetadata       chan spooledMetadata\n\tspooledSegmentsToDecode       chan segmentToDecode\n\tdecodedSegments               chan decodedSegment\n\tsegmentsToProccess            chan segmentToProccess\n\twaitSegmentDecodersWorkers    sync.WaitGroup\n\twaitDownloadSegmentsWorkers   sync.WaitGroup\n\tcancelDownloadWorkers         context.CancelFunc\n\tcancelDecodersWorkers         context.CancelFunc\n\tspoolingRowsChannel           chan []queryData\n\tspoolingProcesserDone         chan struct{}\n\tsegmentThrottleCh             chan struct{}\n\terrors                        chan error\n\tdoneCh                        chan struct{}\n\tsegmentDispatcherDoneCh       chan struct{}\n}\n\ntype segmentToDecode struct {\n\tsegmentIndex int\n\tencoding     string\n\tdata         []byte\n\tmetadata     segmentMetadata\n}\n\ntype decodedSegment struct {\n\trowOffset int64\n\tqueryData []queryData\n}\n\nvar (\n\t_ driver.Stmt              = &driverStmt{}\n\t_ driver.StmtQueryContext  = &driverStmt{}\n\t_ driver.StmtExecContext   = &driverStmt{}\n\t_ driver.NamedValueChecker = &driverStmt{}\n)\n\n// Close closes statement just before releasing connection\nfunc (st *driverStmt) Close() error {\n\tif st.doneCh == nil {\n\t\treturn nil\n\t}\n\tclose(st.doneCh)\n\tif st.statsCh != nil {\n\t\t<-st.statsCh\n\t\tst.statsCh = nil\n\t}\n\tgo func() {\n\t\t// drain errors chan to allow goroutines to write to it\n\t\tfor range st.errors {\n\t\t}\n\t}()\n\n\tfor range st.queryResponses {\n\t}\n\tfor range st.httpResponses {\n\t}\n\n\tif st.cancelDownloadWorkers != nil {\n\t\tst.cancelDownloadWorkers()\n\t}\n\n\tif st.cancelDecodersWorkers != nil {\n\t\tst.cancelDecodersWorkers()\n\t}\n\n\tif st.spoolingRowsChannel != nil {\n\t\tfor range st.spoolingRowsChannel {\n\t\t}\n\t}\n\n\tif st.decodedSegments != nil {\n\t\tfor range st.decodedSegments {\n\t\t}\n\t}\n\n\tif st.spooledSegmentsToDecode != nil {\n\t\tfor range st.spooledSegmentsToDecode {\n\t\t}\n\t}\n\n\tif st.spooledSegmentsMetadata != nil {\n\t\tfor range st.spooledSegmentsMetadata {\n\t\t}\n\t}\n\n\tif st.segmentsToProccess != nil {\n\t\tfor range st.segmentsToProccess {\n\t\t}\n\t}\n\n\tst.waitDownloadSegmentsWorkers.Wait()\n\n\tst.waitSegmentDecodersWorkers.Wait()\n\n\tclose(st.nextURIs)\n\tclose(st.errors)\n\n\tst.doneCh = nil\n\tst.cancelDownloadWorkers = nil\n\tst.spooledSegmentsMetadata = nil\n\tst.spooledSegmentsToDecode = nil\n\tst.cancelDecodersWorkers = nil\n\tst.segmentsToProccess = nil\n\tst.decodedSegments = nil\n\tst.spoolingRowsChannel = nil\n\n\treturn nil\n}\n\nfunc (st *driverStmt) NumInput() int {\n\treturn -1\n}\n\nfunc (st *driverStmt) Exec(args []driver.Value) (driver.Result, error) {\n\treturn nil, driver.ErrSkip\n}\n\nfunc (st *driverStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {\n\tsr, err := st.exec(ctx, args)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trows := &driverRows{\n\t\tctx:          ctx,\n\t\tstmt:         st,\n\t\tqueryID:      sr.ID,\n\t\tnextURI:      sr.NextURI,\n\t\trowsAffected: sr.UpdateCount,\n\t\tstatsCh:      st.statsCh,\n\t\tdoneCh:       st.doneCh,\n\t}\n\t// consume all results, if there are any\n\tfor err == nil {\n\t\terr = rows.fetch()\n\t}\n\n\tif err != nil && err != io.EOF {\n\t\treturn nil, err\n\t}\n\treturn rows, nil\n}\n\nfunc (st *driverStmt) CheckNamedValue(arg *driver.NamedValue) error {\n\tswitch arg.Value.(type) {\n\tcase nil:\n\t\treturn nil\n\tcase Numeric, trinoDate, trinoTime, trinoTimeTz, trinoTimestamp, time.Duration:\n\t\treturn nil\n\tdefault:\n\t\t{\n\t\t\tif reflect.TypeOf(arg.Value).Kind() == reflect.Slice {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tif arg.Name == trinoRoleHeader {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tif arg.Name == trinoProgressCallbackParam {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif arg.Name == trinoProgressCallbackPeriodParam {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn driver.ErrSkip\n}\n\ntype stmtResponse struct {\n\tID          string    `json:\"id\"`\n\tInfoURI     string    `json:\"infoUri\"`\n\tNextURI     string    `json:\"nextUri\"`\n\tStats       stmtStats `json:\"stats\"`\n\tError       ErrTrino  `json:\"error\"`\n\tUpdateType  string    `json:\"updateType\"`\n\tUpdateCount int64     `json:\"updateCount\"`\n}\n\ntype stmtStats struct {\n\tState                string      `json:\"state\"`\n\tScheduled            bool        `json:\"scheduled\"`\n\tNodes                int         `json:\"nodes\"`\n\tTotalSplits          int         `json:\"totalSplits\"`\n\tQueuesSplits         int         `json:\"queuedSplits\"`\n\tRunningSplits        int         `json:\"runningSplits\"`\n\tCompletedSplits      int         `json:\"completedSplits\"`\n\tUserTimeMillis       int         `json:\"userTimeMillis\"`\n\tCPUTimeMillis        int64       `json:\"cpuTimeMillis\"`\n\tWallTimeMillis       int64       `json:\"wallTimeMillis\"`\n\tQueuedTimeMillis     int64       `json:\"queuedTimeMillis\"`\n\tElapsedTimeMillis    int64       `json:\"elapsedTimeMillis\"`\n\tProcessedRows        int64       `json:\"processedRows\"`\n\tProcessedBytes       int64       `json:\"processedBytes\"`\n\tPhysicalInputBytes   int64       `json:\"physicalInputBytes\"`\n\tPhysicalWrittenBytes int64       `json:\"physicalWrittenBytes\"`\n\tPeakMemoryBytes      int64       `json:\"peakMemoryBytes\"`\n\tSpilledBytes         int64       `json:\"spilledBytes\"`\n\tRootStage            stmtStage   `json:\"rootStage\"`\n\tProgressPercentage   jsonFloat64 `json:\"progressPercentage\"`\n\tRunningPercentage    jsonFloat64 `json:\"runningPercentage\"`\n}\n\ntype ErrTrino struct {\n\tMessage       string        `json:\"message\"`\n\tSqlState      string        `json:\"sqlState\"`\n\tErrorCode     int           `json:\"errorCode\"`\n\tErrorName     string        `json:\"errorName\"`\n\tErrorType     string        `json:\"errorType\"`\n\tErrorLocation ErrorLocation `json:\"errorLocation\"`\n\tFailureInfo   FailureInfo   `json:\"failureInfo\"`\n}\n\nfunc (i ErrTrino) Error() string {\n\treturn i.ErrorType + \": \" + i.Message\n}\n\ntype ErrorLocation struct {\n\tLineNumber   int `json:\"lineNumber\"`\n\tColumnNumber int `json:\"columnNumber\"`\n}\n\ntype FailureInfo struct {\n\tType          string        `json:\"type\"`\n\tMessage       string        `json:\"message\"`\n\tCause         *FailureInfo  `json:\"cause\"`\n\tSuppressed    []FailureInfo `json:\"suppressed\"`\n\tStack         []string      `json:\"stack\"`\n\tErrorInfo     ErrorInfo     `json:\"errorInfo\"`\n\tErrorLocation ErrorLocation `json:\"errorLocation\"`\n}\n\ntype ErrorInfo struct {\n\tCode int    `json:\"code\"`\n\tName string `json:\"name\"`\n\tType string `json:\"type\"`\n}\n\nfunc (i ErrorInfo) Error() string {\n\treturn fmt.Sprintf(\"%s: %s (%d)\", i.Type, i.Name, i.Code)\n}\n\ntype stmtStage struct {\n\tStageID         string      `json:\"stageId\"`\n\tState           string      `json:\"state\"`\n\tDone            bool        `json:\"done\"`\n\tNodes           int         `json:\"nodes\"`\n\tTotalSplits     int         `json:\"totalSplits\"`\n\tQueuedSplits    int         `json:\"queuedSplits\"`\n\tRunningSplits   int         `json:\"runningSplits\"`\n\tCompletedSplits int         `json:\"completedSplits\"`\n\tUserTimeMillis  int         `json:\"userTimeMillis\"`\n\tCPUTimeMillis   int         `json:\"cpuTimeMillis\"`\n\tWallTimeMillis  int         `json:\"wallTimeMillis\"`\n\tProcessedRows   int         `json:\"processedRows\"`\n\tProcessedBytes  int         `json:\"processedBytes\"`\n\tSubStages       []stmtStage `json:\"subStages\"`\n}\n\ntype jsonFloat64 float64\n\nfunc (f *jsonFloat64) UnmarshalJSON(data []byte) error {\n\tvar v float64\n\terr := json.Unmarshal(data, &v)\n\tif err != nil {\n\t\tvar jsonErr *json.UnmarshalTypeError\n\t\tif errors.As(err, &jsonErr) {\n\t\t\tif f != nil {\n\t\t\t\t*f = 0\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\tp := (*float64)(f)\n\t*p = v\n\treturn nil\n}\n\nvar _ json.Unmarshaler = new(jsonFloat64)\n\nfunc (st *driverStmt) Query(args []driver.Value) (driver.Rows, error) {\n\treturn nil, driver.ErrSkip\n}\n\nfunc (st *driverStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {\n\tsr, err := st.exec(ctx, args)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trows := &driverRows{\n\t\tctx:     ctx,\n\t\tstmt:    st,\n\t\tqueryID: sr.ID,\n\t\tnextURI: sr.NextURI,\n\t\tstatsCh: st.statsCh,\n\t\tdoneCh:  st.doneCh,\n\t}\n\tif err = rows.fetch(); err != nil && err != io.EOF {\n\t\treturn nil, err\n\t}\n\treturn rows, nil\n}\n\nfunc (st *driverStmt) exec(ctx context.Context, args []driver.NamedValue) (*stmtResponse, error) {\n\tquery := st.query\n\ths := make(http.Header)\n\t// Ensure the server returns timestamps preserving their precision, without truncating them to timestamp(3).\n\ths.Add(\"X-Trino-Client-Capabilities\", \"PARAMETRIC_DATETIME\")\n\n\tif len(args) > 0 {\n\t\tvar ss []string\n\t\tfor _, arg := range args {\n\t\t\tif arg.Name == trinoProgressCallbackParam {\n\t\t\t\tst.conn.progressUpdater = arg.Value.(ProgressUpdater)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif arg.Name == trinoProgressCallbackPeriodParam {\n\t\t\t\tst.conn.progressUpdaterPeriod.Period = arg.Value.(time.Duration)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif st.conn.forwardAuthorizationHeader && arg.Name == accessTokenConfig {\n\t\t\t\ttoken := arg.Value.(string)\n\t\t\t\ths.Add(authorizationHeader, getAuthorization(token))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif arg.Name == trinoEncoding {\n\t\t\t\ths.Add(trinoQueryDataEncodingHeader, arg.Value.(string))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif arg.Name == trinoSpoolingWorkerCount {\n\t\t\t\tnumberOfWorkers, err := strconv.Atoi(arg.Value.(string))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tst.spoolingWorkerCount = numberOfWorkers\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif arg.Name == trinoMaxOutOfOrdersSegments {\n\t\t\t\tmaxSegmentsOutOfOrder, err := strconv.Atoi(arg.Value.(string))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tst.spoolingMaxOutOfOrderSegments = maxSegmentsOutOfOrder\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif strings.HasPrefix(arg.Name, trinoHeaderPrefix) {\n\t\t\t\theaderValue, err := formatHeaderValue(arg.Name, arg.Value)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tif arg.Name == trinoUserHeader {\n\t\t\t\t\tst.user = headerValue\n\t\t\t\t}\n\n\t\t\t\tif arg.Name == trinoRoleHeader {\n\t\t\t\t\tst.conn.httpHeaders.Set(trinoRoleHeader, headerValue)\n\t\t\t\t}\n\n\t\t\t\ths.Add(arg.Name, headerValue)\n\t\t\t} else {\n\t\t\t\ts, err := Serial(arg.Value)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tif st.conn.useExplicitPrepare && hs.Get(preparedStatementHeader) == \"\" {\n\t\t\t\t\tfor _, v := range st.conn.httpHeaders.Values(preparedStatementHeader) {\n\t\t\t\t\t\ths.Add(preparedStatementHeader, v)\n\t\t\t\t\t}\n\t\t\t\t\ths.Add(preparedStatementHeader, preparedStatementName+\"=\"+url.QueryEscape(st.query))\n\t\t\t\t}\n\t\t\t\tss = append(ss, s)\n\t\t\t}\n\t\t}\n\t\tif (st.conn.progressUpdater != nil && st.conn.progressUpdaterPeriod.Period == 0) || (st.conn.progressUpdater == nil && st.conn.progressUpdaterPeriod.Period > 0) {\n\t\t\treturn nil, ErrInvalidProgressCallbackHeader\n\t\t}\n\t\tif len(ss) > 0 {\n\t\t\tif st.conn.useExplicitPrepare {\n\t\t\t\tquery = \"EXECUTE \" + preparedStatementName + \" USING \" + strings.Join(ss, \", \")\n\t\t\t} else {\n\t\t\t\tquery = \"EXECUTE IMMEDIATE \" + formatStringLiteral(st.query) + \" USING \" + strings.Join(ss, \", \")\n\t\t\t}\n\t\t}\n\t}\n\n\tif st.spoolingWorkerCount > st.spoolingMaxOutOfOrderSegments {\n\t\treturn nil, fmt.Errorf(\"spooling worker cannot be greater than max out of order segments allowed. spooling workers: %d, allowed out of order segments: %d\", st.spoolingWorkerCount, st.spoolingMaxOutOfOrderSegments)\n\t}\n\n\tif hs.Get(trinoQueryDataEncodingHeader) == \"\" {\n\t\ths.Add(trinoQueryDataEncodingHeader, defaulttrinoEncoding)\n\t}\n\n\tvar cancel context.CancelFunc = func() {}\n\tif st.conn.queryTimeout != nil {\n\t\tctx, cancel = context.WithTimeout(ctx, *st.conn.queryTimeout)\n\t} else if _, ok := ctx.Deadline(); !ok {\n\t\tctx, cancel = context.WithTimeout(ctx, DefaultQueryTimeout)\n\t}\n\n\treq, err := st.conn.newRequest(ctx, \"POST\", st.conn.baseURL+\"/v1/statement\", strings.NewReader(query), hs)\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, err\n\t}\n\n\tresp, err := st.conn.roundTrip(ctx, req)\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, err\n\t}\n\n\tdefer resp.Body.Close()\n\tvar sr stmtResponse\n\td := json.NewDecoder(resp.Body)\n\td.UseNumber()\n\terr = d.Decode(&sr)\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, fmt.Errorf(\"trino: %w\", err)\n\t}\n\n\tst.doneCh = make(chan struct{})\n\tst.nextURIs = make(chan string)\n\tst.httpResponses = make(chan *http.Response)\n\tst.queryResponses = make(chan queryResponse)\n\tst.errors = make(chan error)\n\tgo func() {\n\t\tdefer close(st.httpResponses)\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase nextURI := <-st.nextURIs:\n\t\t\t\tif nextURI == \"\" {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ths := make(http.Header)\n\t\t\t\ths.Add(trinoUserHeader, st.user)\n\t\t\t\treq, err := st.conn.newRequest(ctx, \"GET\", nextURI, nil, hs)\n\t\t\t\tif err != nil {\n\t\t\t\t\tif ctx.Err() == context.Canceled {\n\t\t\t\t\t\tst.errors <- context.Canceled\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tst.errors <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tresp, err := st.conn.roundTrip(ctx, req)\n\t\t\t\tif err != nil {\n\t\t\t\t\tif ctx.Err() == context.Canceled {\n\t\t\t\t\t\tst.errors <- context.Canceled\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tst.errors <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tselect {\n\t\t\t\tcase st.httpResponses <- resp:\n\t\t\t\tcase <-st.doneCh:\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\tcase <-st.doneCh:\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\tgo func() {\n\t\tdefer close(st.queryResponses)\n\t\tdefer cancel()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase resp := <-st.httpResponses:\n\t\t\t\tif resp == nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tvar qresp queryResponse\n\t\t\t\td := json.NewDecoder(resp.Body)\n\t\t\t\td.UseNumber()\n\t\t\t\terr = d.Decode(&qresp)\n\t\t\t\tif err != nil {\n\t\t\t\t\tst.errors <- fmt.Errorf(\"trino: %w\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\terr = resp.Body.Close()\n\t\t\t\tif err != nil {\n\t\t\t\t\tst.errors <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\terr = handleResponseError(resp.StatusCode, qresp.Error)\n\t\t\t\tif err != nil {\n\t\t\t\t\tst.errors <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tselect {\n\t\t\t\tcase st.nextURIs <- qresp.NextURI:\n\t\t\t\tcase <-st.doneCh:\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tselect {\n\t\t\t\tcase st.queryResponses <- qresp:\n\t\t\t\tcase <-st.doneCh:\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\tcase <-st.doneCh:\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\tst.nextURIs <- sr.NextURI\n\tif st.conn.progressUpdater != nil {\n\t\tst.statsCh = make(chan QueryProgressInfo)\n\n\t\t// progress updater go func\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase stats := <-st.statsCh:\n\t\t\t\t\tst.conn.progressUpdater.Update(stats)\n\t\t\t\tcase <-st.doneCh:\n\t\t\t\t\tclose(st.statsCh)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\t// initial progress callback call\n\t\tsrStats := QueryProgressInfo{\n\t\t\tQueryId:    sr.ID,\n\t\t\tQueryStats: sr.Stats,\n\t\t}\n\t\tselect {\n\t\tcase st.statsCh <- srStats:\n\t\tdefault:\n\t\t\t// ignore when can't send stats\n\t\t}\n\t\tst.conn.progressUpdaterPeriod.LastCallbackTime = time.Now()\n\t\tst.conn.progressUpdaterPeriod.LastQueryState = sr.Stats.State\n\t}\n\treturn &sr, handleResponseError(resp.StatusCode, sr.Error)\n}\n\ntype SegmentFetcher struct {\n\tctx             context.Context\n\thttpClient      http.Client\n\tspooledMetadata spooledMetadata\n}\n\nfunc (sf *SegmentFetcher) roundTrip(req *http.Request) (*http.Response, error) {\n\tdelay := 200 * time.Millisecond\n\tconst maxRetries = 5\n\n\tretries := 0\n\ttimer := time.NewTimer(0)\n\tdefer timer.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-timer.C:\n\t\t\tresp, err := sf.httpClient.Do(req)\n\t\t\tif err != nil {\n\t\t\t\tvar netErr net.Error\n\n\t\t\t\tif errors.As(err, &netErr) && netErr.Timeout() {\n\t\t\t\t\tretries++\n\t\t\t\t\tif retries > maxRetries {\n\t\t\t\t\t\treturn nil, &ErrQueryFailed{Reason: fmt.Errorf(\"max retries reached: %w\", err)}\n\t\t\t\t\t}\n\t\t\t\t\tdelay = time.Duration(float64(delay) * math.Phi)\n\t\t\t\t\ttimer.Reset(delay)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\treturn nil, &ErrQueryFailed{Reason: err}\n\t\t\t}\n\n\t\t\tswitch resp.StatusCode {\n\t\t\tcase http.StatusOK:\n\t\t\t\treturn resp, nil\n\n\t\t\tcase http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:\n\t\t\t\tresp.Body.Close()\n\t\t\t\tretries++\n\t\t\t\tif retries > maxRetries {\n\t\t\t\t\treturn nil, &ErrQueryFailed{Reason: fmt.Errorf(\"max retries reached for status code %d\", resp.StatusCode)}\n\t\t\t\t}\n\t\t\t\tdelay = time.Duration(float64(delay) * math.Phi)\n\t\t\t\ttimer.Reset(delay)\n\t\t\t\tcontinue\n\n\t\t\tdefault:\n\t\t\t\treturn nil, newErrQueryFailedFromResponse(resp)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (sf *SegmentFetcher) fetchSegment() ([]byte, error) {\n\treq, err := http.NewRequestWithContext(sf.ctx, \"GET\", sf.spooledMetadata.uri, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor k, v := range sf.spooledMetadata.headers {\n\t\theaderSlice, ok := v.([]interface{})\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"unsupported header type %T\", v)\n\t\t}\n\n\t\tif len(headerSlice) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(headerSlice) > 1 {\n\t\t\treturn nil, fmt.Errorf(\"multiple values for header %s\", k)\n\t\t}\n\n\t\theader, ok := headerSlice[0].(string)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"unsupported header value type %T\", headerSlice[0])\n\t\t}\n\t\treq.Header.Add(k, header)\n\t}\n\n\tresp, err := sf.roundTrip(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error fetching segment from uri '%s': %v\", sf.spooledMetadata.uri, err)\n\t}\n\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading response body: %v\", err)\n\t}\n\n\t//acknowledge the segment read\n\tgo func() {\n\t\t// TODO: handle ack erros\n\t\tackReq, err := http.NewRequestWithContext(sf.ctx, \"GET\", sf.spooledMetadata.ackUri, nil)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tfor k, values := range req.Header {\n\t\t\tfor _, v := range values {\n\t\t\t\tackReq.Header.Add(k, v)\n\t\t\t}\n\t\t}\n\n\t\tresp, err := sf.httpClient.Do(ackReq)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tresp.Body.Close()\n\t}()\n\n\treturn data, nil\n}\n\nfunc formatStringLiteral(query string) string {\n\treturn \"'\" + strings.ReplaceAll(query, \"'\", \"''\") + \"'\"\n}\n\ntype driverRows struct {\n\tctx     context.Context\n\tstmt    *driverStmt\n\tqueryID string\n\tnextURI string\n\n\terr          error\n\trowindex     int\n\tcolumns      []string\n\tcoltype      []*typeConverter\n\tdata         []queryData\n\trowsAffected int64\n\n\tstatsCh chan QueryProgressInfo\n\tdoneCh  chan struct{}\n}\n\nvar _ driver.Rows = &driverRows{}\nvar _ driver.Result = &driverRows{}\nvar _ driver.RowsColumnTypeScanType = &driverRows{}\nvar _ driver.RowsColumnTypeDatabaseTypeName = &driverRows{}\nvar _ driver.RowsColumnTypeLength = &driverRows{}\nvar _ driver.RowsColumnTypePrecisionScale = &driverRows{}\n\n// Close closes the rows iterator.\nfunc (qr *driverRows) Close() error {\n\tif qr.err == sql.ErrNoRows || qr.err == io.EOF {\n\t\treturn nil\n\t}\n\tqr.err = io.EOF\n\tif !qr.stmt.usingSpooledProtocol {\n\t\terr := qr.fetch()\n\t\tif err != nil && err != io.EOF {\n\t\t\treturn err\n\t\t}\n\t\tif qr.nextURI == \"\" {\n\t\t\treturn nil\n\t\t}\n\t} else {\n\t\tselect {\n\t\tcase _, ok := <-qr.stmt.spoolingRowsChannel:\n\t\t\tif !ok {\n\t\t\t\t// channel is closed, all data has been consumed\n\t\t\t\treturn nil\n\t\t\t}\n\t\tcase <-time.NewTimer(100 * time.Millisecond).C:\n\t\t\t// no data is ready\n\t\t}\n\t}\n\ths := make(http.Header)\n\tif qr.stmt.user != \"\" {\n\t\ths.Add(trinoUserHeader, qr.stmt.user)\n\t}\n\tctx, cancel := context.WithTimeout(context.WithoutCancel(qr.ctx), DefaultCancelQueryTimeout)\n\tdefer cancel()\n\treq, err := qr.stmt.conn.newRequest(ctx, \"DELETE\", qr.stmt.conn.baseURL+\"/v1/query/\"+url.PathEscape(qr.queryID), nil, hs)\n\tif err != nil {\n\t\treturn err\n\t}\n\tresp, err := qr.stmt.conn.roundTrip(ctx, req)\n\tif err != nil {\n\t\tqferr, ok := err.(*ErrQueryFailed)\n\t\tif ok && qferr.StatusCode == http.StatusNoContent {\n\t\t\tqr.nextURI = \"\"\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\tresp.Body.Close()\n\treturn qr.err\n}\n\n// Columns returns the names of the columns.\nfunc (qr *driverRows) Columns() []string {\n\tif qr.err != nil {\n\t\treturn []string{}\n\t}\n\tif qr.columns == nil {\n\t\tif err := qr.fetch(); err != nil && err != io.EOF {\n\t\t\tqr.err = err\n\t\t\treturn []string{}\n\t\t}\n\t}\n\treturn qr.columns\n}\n\nfunc (qr *driverRows) ColumnTypeDatabaseTypeName(index int) string {\n\ttypeName := qr.coltype[index].parsedType[0]\n\tif typeName == \"map\" || typeName == \"array\" || typeName == \"row\" {\n\t\ttypeName = qr.coltype[index].typeName\n\t}\n\treturn strings.ToUpper(typeName)\n}\n\nfunc (qr *driverRows) ColumnTypeScanType(index int) reflect.Type {\n\treturn qr.coltype[index].scanType\n}\n\nfunc (qr *driverRows) ColumnTypeLength(index int) (int64, bool) {\n\treturn qr.coltype[index].size.value, qr.coltype[index].size.hasValue\n}\n\nfunc (qr *driverRows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) {\n\treturn qr.coltype[index].precision.value, qr.coltype[index].scale.value, qr.coltype[index].precision.hasValue\n}\n\n// Next is called to populate the next row of data into\n// the provided slice. The provided slice will be the same\n// size as the Columns() are wide.\n//\n// Next should return io.EOF when there are no more rows.\nfunc (qr *driverRows) Next(dest []driver.Value) error {\n\tif qr.err != nil {\n\t\treturn qr.err\n\t}\n\tif !qr.stmt.usingSpooledProtocol && (qr.columns == nil || qr.rowindex >= len(qr.data)) {\n\t\tif qr.nextURI == \"\" {\n\t\t\tqr.err = io.EOF\n\t\t\treturn qr.err\n\t\t}\n\t\tif err := qr.fetch(); err != nil {\n\t\t\tqr.err = err\n\t\t\treturn err\n\t\t}\n\t} else if qr.stmt.usingSpooledProtocol && (qr.rowindex >= len(qr.data) || qr.data == nil) {\n\t\tvar ok bool\n\t\tselect {\n\t\t// The spoolingRowsChannel is initialized in startSpoolingProtocolWorkers,\n\t\t// which is called by fetch() when the first query response indicates\n\t\t// the spooling protocol (i.e., the response contains segments).\n\t\t// At that point, usingSpooledProtocol is set to true and the channel is created.\n\t\tcase qr.data, ok = <-qr.stmt.spoolingRowsChannel:\n\t\t\tif !ok {\n\t\t\t\tqr.err = io.EOF\n\t\t\t\treturn qr.err\n\t\t\t}\n\n\t\t\tqr.rowindex = 0\n\n\t\tcase err := <-qr.stmt.errors:\n\t\t\tif err == nil {\n\t\t\t\t// Channel was closed, which means the statement\n\t\t\t\t// or rows were closed.\n\t\t\t\tqr.err = io.EOF\n\t\t\t\treturn qr.err\n\t\t\t} else if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {\n\t\t\t\tqr.Close()\n\t\t\t}\n\t\t\tqr.stmt.cancelDecodersWorkers()\n\t\t\tqr.stmt.cancelDownloadWorkers()\n\t\t\tqr.err = err\n\t\t\treturn qr.err\n\t\t}\n\t}\n\n\treturn qr.next(dest)\n}\n\nfunc (qr *driverRows) next(dest []driver.Value) error {\n\tif len(qr.coltype) == 0 {\n\t\tqr.err = sql.ErrNoRows\n\t\treturn qr.err\n\t}\n\tfor i, v := range qr.coltype {\n\t\tif i > len(dest)-1 {\n\t\t\tbreak\n\t\t}\n\t\tvv, err := v.ConvertValue(qr.data[qr.rowindex][i])\n\t\tif err != nil {\n\t\t\tqr.err = err\n\t\t\treturn err\n\t\t}\n\t\tdest[i] = vv\n\t}\n\tqr.rowindex++\n\treturn nil\n}\n\n// LastInsertId returns the database's auto-generated ID\n// after, for example, an INSERT into a table with primary\n// key.\nfunc (qr driverRows) LastInsertId() (int64, error) {\n\treturn 0, ErrOperationNotSupported\n}\n\n// RowsAffected returns the number of rows affected by the query.\nfunc (qr driverRows) RowsAffected() (int64, error) {\n\treturn qr.rowsAffected, nil\n}\n\ntype queryResponse struct {\n\tID               string        `json:\"id\"`\n\tInfoURI          string        `json:\"infoUri\"`\n\tPartialCancelURI string        `json:\"partialCancelUri\"`\n\tNextURI          string        `json:\"nextUri\"`\n\tColumns          []queryColumn `json:\"columns\"`\n\tData             interface{}   `json:\"data\"`\n\tStats            stmtStats     `json:\"stats\"`\n\tError            ErrTrino      `json:\"error\"`\n\tUpdateType       string        `json:\"updateType\"`\n\tUpdateCount      int64         `json:\"updateCount\"`\n}\n\ntype segmentMetadata struct {\n\trowOffset        int64\n\trowsCount        int64\n\tsegmentSize      int64\n\tuncompressedSize int64\n}\n\ntype spooledMetadata struct {\n\turi      string\n\tackUri   string\n\tencoding string\n\theaders  map[string]interface{}\n\tmetadata segmentMetadata\n}\n\nfunc parseSpooledMetadata(segment map[string]interface{}, segmentIndex int, segmentMetadata segmentMetadata, encoding string) (spooledMetadata, error) {\n\tresult := spooledMetadata{\n\t\tmetadata: segmentMetadata,\n\t\tencoding: encoding,\n\t\theaders:  make(map[string]interface{}),\n\t}\n\n\tvar ok bool\n\tresult.uri, ok = segment[\"uri\"].(string)\n\tif !ok || result.uri == \"\" {\n\t\treturn spooledMetadata{}, fmt.Errorf(\"missing or invalid 'uri' field in spooled segment at index %d\", segmentIndex)\n\t}\n\n\tresult.ackUri, ok = segment[\"ackUri\"].(string)\n\tif !ok || result.ackUri == \"\" {\n\t\treturn spooledMetadata{}, fmt.Errorf(\"missing or invalid 'ackUri' field in spooled segment at index %d\", segmentIndex)\n\t}\n\n\tif rawHeaders, exists := segment[\"headers\"]; exists {\n\t\tresult.headers, ok = rawHeaders.(map[string]interface{})\n\t\tif !ok {\n\t\t\treturn spooledMetadata{}, fmt.Errorf(\"invalid 'headers' field in spooled segment at index %d: expected map[string]interface{}\", segmentIndex)\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\nfunc parseSegmentMetadata(metadata map[string]interface{}) (segmentMetadata, error) {\n\tresult := segmentMetadata{\n\t\trowOffset:        0,\n\t\trowsCount:        0,\n\t\tsegmentSize:      0,\n\t\tuncompressedSize: 0,\n\t}\n\n\tvar err error\n\t// Mandatory field\n\tif result.rowOffset, err = getInt64(metadata, \"rowOffset\"); err != nil {\n\t\treturn segmentMetadata{}, err\n\t}\n\n\t// Mandatory field\n\tif result.segmentSize, err = getInt64(metadata, \"segmentSize\"); err != nil {\n\t\treturn segmentMetadata{}, err\n\t}\n\n\tif result.uncompressedSize, err = getOptionalInt64(metadata, \"uncompressedSize\"); err != nil {\n\t\treturn segmentMetadata{}, err\n\t}\n\n\t// Bug: rowsCount was wrongly not enforced as a mandatory field on Trino response. Fixed on 475 release\n\tif result.rowsCount, err = getOptionalInt64(metadata, \"rowsCount\"); err != nil {\n\t\treturn segmentMetadata{}, err\n\t}\n\n\treturn result, nil\n}\n\nfunc getInt64(metadata map[string]interface{}, key string) (int64, error) {\n\tval, exists := metadata[key]\n\tif !exists {\n\t\treturn 0, fmt.Errorf(\"%s is missing in segment metadata\", key)\n\t}\n\n\treturn parseInt64(val, key)\n}\n\nfunc getOptionalInt64(metadata map[string]interface{}, key string) (int64, error) {\n\tval, exists := metadata[key]\n\tif !exists {\n\t\treturn 0, nil\n\t}\n\n\treturn parseInt64(val, key)\n}\n\nfunc parseInt64(val interface{}, key string) (int64, error) {\n\tnum, ok := val.(json.Number)\n\tif !ok {\n\t\treturn 0, fmt.Errorf(\"invalid type for %s in segment metadata, expected json.Number, got %T\", key, val)\n\t}\n\n\tn, err := num.Int64()\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"error converting %s to int64: %v\", key, err)\n\t}\n\n\treturn n, nil\n}\n\nfunc decodeSegment(data []byte, encoding string, metadata segmentMetadata) ([]queryData, error) {\n\tif int64(len(data)) != metadata.segmentSize {\n\t\treturn nil, fmt.Errorf(\"segment size mismatch: expected %d bytes, got %d bytes\", metadata.segmentSize, len(data))\n\t}\n\n\tdecompressedSegment, err := decompressSegment(data, encoding, metadata)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar queryDataList = make([]queryData, metadata.rowsCount)\n\tdecoder := json.NewDecoder(bytes.NewReader(decompressedSegment))\n\tdecoder.UseNumber()\n\terr = decoder.Decode(&queryDataList)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode segment into JSON at rowOffset %d: %v\", metadata.rowOffset, err)\n\t}\n\n\treturn queryDataList, nil\n}\n\nfunc decompressSegment(data []byte, encoding string, metadata segmentMetadata) ([]byte, error) {\n\tif metadata.uncompressedSize == 0 {\n\t\treturn data, nil\n\t}\n\n\tvar decompressedData []byte\n\tswitch encoding {\n\tcase \"json+zstd\":\n\t\tzstdDecoder, err := zstd.NewReader(nil)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error creating zstd reader: %w\", err)\n\t\t}\n\t\tdefer zstdDecoder.Close()\n\t\tdst := make([]byte, 0, metadata.uncompressedSize)\n\t\tdecompressedData, err = zstdDecoder.DecodeAll(data, dst)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to decompress zstd segment at rowOffset %d: %v\", metadata.rowOffset, err)\n\t\t}\n\tcase \"json+lz4\":\n\t\tdecompressedData = make([]byte, metadata.uncompressedSize)\n\n\t\tn, err := lz4.UncompressBlock(data, decompressedData)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to decompress LZ4 segment at rowOffset %d: %v\", metadata.rowOffset, err)\n\t\t}\n\n\t\tdecompressedData = decompressedData[:n]\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported segment encoder: %s\", encoding)\n\t}\n\n\tif int64(len(decompressedData)) != metadata.uncompressedSize {\n\t\treturn nil, fmt.Errorf(\"decompressed size mismatch: expected %d bytes, got %d bytes\", metadata.uncompressedSize, len(decompressedData))\n\t}\n\n\treturn decompressedData, nil\n}\n\ntype queryColumn struct {\n\tName          string        `json:\"name\"`\n\tType          string        `json:\"type\"`\n\tTypeSignature typeSignature `json:\"typeSignature\"`\n}\n\ntype queryData []interface{}\n\ntype namedTypeSignature struct {\n\tFieldName     rowFieldName  `json:\"fieldName\"`\n\tTypeSignature typeSignature `json:\"typeSignature\"`\n}\n\ntype rowFieldName struct {\n\tName string `json:\"name\"`\n}\n\ntype typeSignature struct {\n\tRawType   string         `json:\"rawType\"`\n\tArguments []typeArgument `json:\"arguments\"`\n}\n\ntype typeKind string\n\nconst (\n\tKIND_TYPE       = typeKind(\"TYPE\")\n\tKIND_NAMED_TYPE = typeKind(\"NAMED_TYPE\")\n\tKIND_LONG       = typeKind(\"LONG\")\n\tKIND_VARIABLE   = typeKind(\"VARIABLE\")\n)\n\ntype typeArgument struct {\n\t// Kind determines if the typeSignature, namedTypeSignature, or long field has a value\n\tKind  typeKind        `json:\"kind\"`\n\tValue json.RawMessage `json:\"value\"`\n\t// typeSignature decoded from Value when Kind is TYPE\n\ttypeSignature typeSignature\n\t// namedTypeSignature decoded from Value when Kind is NAMED_TYPE\n\tnamedTypeSignature namedTypeSignature\n\t// long decoded from Value when Kind is LONG\n\tlong int64\n}\n\nfunc handleResponseError(status int, respErr ErrTrino) error {\n\tswitch respErr.ErrorName {\n\tcase \"\":\n\t\treturn nil\n\tcase \"USER_CANCELLED\":\n\t\treturn ErrQueryCancelled\n\tdefault:\n\t\treturn &ErrQueryFailed{\n\t\t\tStatusCode: status,\n\t\t\tReason:     &respErr,\n\t\t}\n\t}\n}\n\nfunc (qr *driverRows) startOrderedSegmentStreamer() {\n\tgo func() {\n\t\tdefer close(qr.stmt.spoolingRowsChannel)\n\t\tdefer close(qr.stmt.spoolingProcesserDone)\n\n\t\tconsumed := 0\n\t\tbuffer := make([]decodedSegment, 0, qr.stmt.spoolingMaxOutOfOrderSegments)\n\t\tvar nextExpectedOffset int64 = 0\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase segment, ok := <-qr.stmt.decodedSegments:\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tbuffer = append(buffer, segment)\n\n\t\t\t\tif nextExpectedOffset != segment.rowOffset {\n\t\t\t\t\tif len(buffer) >= qr.stmt.spoolingMaxOutOfOrderSegments {\n\t\t\t\t\t\tqr.stmt.errors <- fmt.Errorf(\n\t\t\t\t\t\t\t\"all %d out-of-order segments buffered (limit: %d). This indicates a bug or inconsistency in the segments metadata response (e.g., missing, duplicate, or misordered segments, or row offsets not matching the expected sequence)\",\n\t\t\t\t\t\t\tlen(buffer), qr.stmt.spoolingMaxOutOfOrderSegments)\n\t\t\t\t\t}\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tconsumed = 0\n\t\t\t\tslices.SortFunc(buffer, func(a, b decodedSegment) int {\n\t\t\t\t\tif a.rowOffset < b.rowOffset {\n\t\t\t\t\t\treturn -1\n\t\t\t\t\t}\n\t\t\t\t\tif a.rowOffset > b.rowOffset {\n\t\t\t\t\t\treturn 1\n\t\t\t\t\t}\n\t\t\t\t\treturn 0\n\t\t\t\t})\n\n\t\t\t\tfor consumed < len(buffer) && buffer[consumed].rowOffset == nextExpectedOffset {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase qr.stmt.spoolingRowsChannel <- buffer[consumed].queryData:\n\t\t\t\t\tcase <-qr.doneCh:\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// release reserved slot\n\t\t\t\t\tselect {\n\t\t\t\t\tcase <-qr.stmt.segmentThrottleCh:\n\t\t\t\t\tcase <-qr.doneCh:\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tnextExpectedOffset += int64(len(buffer[consumed].queryData))\n\t\t\t\t\tconsumed++\n\t\t\t\t}\n\n\t\t\t\tcopy(buffer[0:], buffer[consumed:])\n\t\t\t\tbuffer = buffer[:len(buffer)-consumed]\n\n\t\t\tcase <-qr.doneCh:\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (qr *driverRows) fetch() error {\n\tvar qresp queryResponse\n\tvar err error\n\tfor {\n\t\tselect {\n\t\tcase qresp = <-qr.stmt.queryResponses:\n\t\t\tif qresp.ID == \"\" {\n\t\t\t\treturn io.EOF\n\t\t\t}\n\n\t\t\terr = qr.initColumns(&qresp)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tqr.rowindex = 0\n\t\t\tqr.nextURI = qresp.NextURI\n\t\t\tswitch data := qresp.Data.(type) {\n\t\t\tcase []interface{}:\n\t\t\t\t// direct protocol\n\t\t\t\tqr.data = make([]queryData, len(data))\n\t\t\t\tfor i, item := range data {\n\t\t\t\t\tif row, ok := item.([]interface{}); ok {\n\t\t\t\t\t\tqr.data[i] = row\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn fmt.Errorf(\"unexpected data type for row at index %d: expected []interface{}, got %T\", i, item)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase map[string]interface{}:\n\t\t\t\t// spooling protocol\n\t\t\t\tqr.stmt.startSpoolingProtocolWorkers(qr.ctx)\n\t\t\t\tqr.startOrderedSegmentStreamer()\n\n\t\t\t\terr := qr.queueSpoolingSegments(data)\n\t\t\t\tqr.proccessSpollingSegments()\n\n\t\t\t\treturn err\n\t\t\tcase nil:\n\t\t\t\tqr.data = nil\n\t\t\t}\n\t\t\tqr.rowsAffected = qresp.UpdateCount\n\t\t\tqr.scheduleProgressUpdate(qresp.ID, qresp.Stats)\n\t\t\tif len(qr.data) != 0 {\n\t\t\t\treturn nil\n\t\t\t}\n\t\tcase err = <-qr.stmt.errors:\n\t\t\tif err == nil {\n\t\t\t\t// Channel was closed, which means the statement\n\t\t\t\t// or rows were closed.\n\t\t\t\terr = io.EOF\n\t\t\t} else if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {\n\t\t\t\tqr.Close()\n\t\t\t}\n\t\t\tqr.err = err\n\t\t\treturn err\n\t\t}\n\t}\n}\n\nfunc (st *driverStmt) startSpoolingProtocolWorkers(ctx context.Context) {\n\tst.usingSpooledProtocol = true\n\n\tif st.spoolingWorkerCount == 0 {\n\t\tst.spoolingWorkerCount = defaultSpoolingDownloadWorkers\n\t}\n\n\tif st.spoolingMaxOutOfOrderSegments == 0 {\n\t\tst.spoolingMaxOutOfOrderSegments = defaultallowedOutOfOrder\n\t}\n\n\tdownloadSegmentsCtx, cancelDownloadWorkers := context.WithCancel(context.WithoutCancel(ctx))\n\tst.cancelDownloadWorkers = cancelDownloadWorkers\n\tdecodeSegmentCtx, cancelDecodersWorkers := context.WithCancel(context.WithoutCancel(ctx))\n\tst.cancelDecodersWorkers = cancelDecodersWorkers\n\n\tst.segmentsToProccess = make(chan segmentToProccess, 1000)\n\tst.spooledSegmentsMetadata = make(chan spooledMetadata, st.spoolingMaxOutOfOrderSegments)\n\tst.spooledSegmentsToDecode = make(chan segmentToDecode, st.spoolingMaxOutOfOrderSegments)\n\tst.spoolingRowsChannel = make(chan []queryData)\n\tst.spoolingProcesserDone = make(chan struct{})\n\tst.segmentDispatcherDoneCh = make(chan struct{})\n\tst.segmentThrottleCh = make(chan struct{}, st.spoolingMaxOutOfOrderSegments)\n\tst.decodedSegments = make(chan decodedSegment)\n\n\tst.startSegmentDispatcher()\n\tst.startDownloadSegmentsWorkers(downloadSegmentsCtx)\n\tst.startSegmentsDecodersWorkers(decodeSegmentCtx)\n}\n\nfunc (st *driverStmt) startSegmentDispatcher() {\n\tgo func() {\n\t\tdefer close(st.segmentDispatcherDoneCh)\n\t\tdefer close(st.segmentThrottleCh)\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase segmentToProccess, ok := <-st.segmentsToProccess:\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// segmentThrottleCh blocks if there are too many out-of-order segments.\n\t\t\t\t// Once all currently downloaded segments are downloaded, decoded,\n\t\t\t\t// and can be ordered, this channel will be drained.\n\t\t\t\tselect {\n\t\t\t\tcase st.segmentThrottleCh <- struct{}{}:\n\t\t\t\tcase <-st.doneCh:\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tsegmentMetadata, exists := segmentToProccess.segment[\"metadata\"]\n\t\t\t\tif !exists {\n\t\t\t\t\tst.errors <- fmt.Errorf(\"metadata is missing in segment at index %d\", segmentToProccess.segmentIndex)\n\t\t\t\t}\n\n\t\t\t\ttypedMetadata, ok := segmentMetadata.(map[string]interface{})\n\t\t\t\tif !ok {\n\t\t\t\t\tst.errors <- fmt.Errorf(\"metadata is invalid or cannot be parsed as map[string]interface{} in segment at index %d\", segmentToProccess.segmentIndex)\n\t\t\t\t}\n\n\t\t\t\tmetadata, err := parseSegmentMetadata(typedMetadata)\n\n\t\t\t\tif err != nil {\n\t\t\t\t\tst.errors <- err\n\t\t\t\t}\n\t\t\t\tswitch segmentToProccess.segment[\"type\"] {\n\t\t\t\tcase \"inline\":\n\t\t\t\t\tdecodedBytes, err := base64.StdEncoding.DecodeString(segmentToProccess.segment[\"data\"].(string))\n\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tst.errors <- fmt.Errorf(\"error decoding base64 data in inline segment at index %d: %v\", segmentToProccess.segmentIndex, err)\n\t\t\t\t\t}\n\n\t\t\t\t\tst.spooledSegmentsToDecode <- segmentToDecode{\n\t\t\t\t\t\tsegmentIndex: 0,\n\t\t\t\t\t\tencoding:     segmentToProccess.encoding,\n\t\t\t\t\t\tdata:         decodedBytes,\n\t\t\t\t\t\tmetadata:     metadata,\n\t\t\t\t\t}\n\n\t\t\t\tcase \"spooled\":\n\t\t\t\t\tspooledMetadata, err := parseSpooledMetadata(segmentToProccess.segment, 0, metadata, segmentToProccess.encoding)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tst.errors <- err\n\t\t\t\t\t}\n\n\t\t\t\t\tst.spooledSegmentsMetadata <- spooledMetadata\n\t\t\t\t}\n\n\t\t\tcase <-st.doneCh:\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (st *driverStmt) startDownloadSegmentsWorkers(ctx context.Context) {\n\tst.waitDownloadSegmentsWorkers.Add(st.spoolingWorkerCount)\n\tfor i := 0; i < st.spoolingWorkerCount; i++ {\n\t\tgo func() {\n\t\t\tdefer st.waitDownloadSegmentsWorkers.Done()\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase metadata, ok := <-st.spooledSegmentsMetadata:\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tsegmentFetcher := &SegmentFetcher{\n\t\t\t\t\t\tctx:             ctx,\n\t\t\t\t\t\thttpClient:      st.conn.httpClient,\n\t\t\t\t\t\tspooledMetadata: metadata,\n\t\t\t\t\t}\n\n\t\t\t\t\tsegment, err := segmentFetcher.fetchSegment()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tst.errors <- err\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tselect {\n\t\t\t\t\tcase st.spooledSegmentsToDecode <- segmentToDecode{\n\t\t\t\t\t\tencoding: metadata.encoding,\n\t\t\t\t\t\tdata:     segment,\n\t\t\t\t\t\tmetadata: metadata.metadata,\n\t\t\t\t\t}:\n\t\t\t\t\tcase <-st.doneCh:\n\t\t\t\t\t\treturn\n\t\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\tcase <-st.doneCh:\n\t\t\t\t\treturn\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n}\n\nfunc (st *driverStmt) startSegmentsDecodersWorkers(ctx context.Context) {\n\tst.waitSegmentDecodersWorkers.Add(st.spoolingWorkerCount)\n\tfor i := 0; i < st.spoolingWorkerCount; i++ {\n\t\tgo func() {\n\t\t\tdefer st.waitSegmentDecodersWorkers.Done()\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase segmentToDecode, ok := <-st.spooledSegmentsToDecode:\n\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tsegment, err := decodeSegment(segmentToDecode.data, segmentToDecode.encoding, segmentToDecode.metadata)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tst.cancelDecodersWorkers()\n\t\t\t\t\t\tst.errors <- fmt.Errorf(\"failed to decode spooled segment at index %d: %v\", segmentToDecode.segmentIndex, err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tselect {\n\t\t\t\t\tcase st.decodedSegments <- decodedSegment{\n\t\t\t\t\t\trowOffset: segmentToDecode.metadata.rowOffset,\n\t\t\t\t\t\tqueryData: segment,\n\t\t\t\t\t}:\n\t\t\t\t\tcase <-st.doneCh:\n\t\t\t\t\t\treturn\n\t\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\tcase <-st.doneCh:\n\t\t\t\t\treturn\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n}\n\nfunc (qr *driverRows) proccessSpollingSegments() {\n\tgo func() {\n\t\tvar qresp queryResponse\n\t\tvar err error\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase qresp = <-qr.stmt.queryResponses:\n\n\t\t\t\tif qresp.ID == \"\" {\n\t\t\t\t\tqr.waitForAllSpoolingWorkersFinish()\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\terr = qr.initColumns(&qresp)\n\t\t\t\tif err != nil {\n\t\t\t\t\tqr.stmt.errors <- err\n\t\t\t\t}\n\n\t\t\t\tswitch data := qresp.Data.(type) {\n\t\t\t\tcase map[string]interface{}:\n\t\t\t\t\tif err := qr.queueSpoolingSegments(data); err != nil {\n\t\t\t\t\t\tqr.stmt.errors <- err\n\t\t\t\t\t}\n\n\t\t\t\tcase nil:\n\t\t\t\t\t// do nothing: trino response without data (e.g only status information)\n\t\t\t\tdefault:\n\t\t\t\t\tqr.stmt.errors <- fmt.Errorf(\"unexpected data type for row at index %s: expected map[string]interface{}, got %T\", qresp.ID, data)\n\t\t\t\t}\n\t\t\t\tqr.scheduleProgressUpdate(qresp.ID, qresp.Stats)\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (qr *driverRows) waitForAllSpoolingWorkersFinish() {\n\tclose(qr.stmt.segmentsToProccess)\n\t<-qr.stmt.segmentDispatcherDoneCh\n\tclose(qr.stmt.spooledSegmentsMetadata)\n\tqr.stmt.waitDownloadSegmentsWorkers.Wait()\n\tclose(qr.stmt.spooledSegmentsToDecode)\n\tqr.stmt.waitSegmentDecodersWorkers.Wait()\n\tclose(qr.stmt.decodedSegments)\n\t<-qr.stmt.spoolingProcesserDone\n}\n\ntype segmentToProccess struct {\n\tsegmentIndex int\n\tencoding     string\n\tsegment      map[string]interface{}\n}\n\nfunc (qr *driverRows) queueSpoolingSegments(data map[string]interface{}) error {\n\tencoding, ok := data[\"encoding\"].(string)\n\tif !ok {\n\t\treturn fmt.Errorf(\"invalid or missing 'encoding' field on spooling protocol, expected string\")\n\t}\n\n\tsegments, ok := data[\"segments\"].([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"invalid or missing 'segments' field on spooling protocol, expected []interface{}\")\n\t}\n\n\tfor segmentIndex, segment := range segments {\n\t\tsegment, ok := segment.(map[string]interface{})\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"segment at index %d is invalid: expected map[string]interface{}, got %T\", segmentIndex, segment)\n\t\t}\n\n\t\tqr.stmt.segmentsToProccess <- segmentToProccess{\n\t\t\tsegmentIndex: segmentIndex,\n\t\t\tencoding:     encoding,\n\t\t\tsegment:      segment,\n\t\t}\n\n\t}\n\n\treturn nil\n}\n\nfunc unmarshalArguments(signature *typeSignature) error {\n\tfor i, argument := range signature.Arguments {\n\t\tvar payload interface{}\n\t\tswitch argument.Kind {\n\t\tcase KIND_TYPE:\n\t\t\tpayload = &(signature.Arguments[i].typeSignature)\n\t\tcase KIND_NAMED_TYPE:\n\t\t\tpayload = &(signature.Arguments[i].namedTypeSignature)\n\t\tcase KIND_LONG:\n\t\t\tpayload = &(signature.Arguments[i].long)\n\t\t}\n\t\terr := json.Unmarshal(argument.Value, payload)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tswitch argument.Kind {\n\t\tcase KIND_TYPE:\n\t\t\terr = unmarshalArguments(&(signature.Arguments[i].typeSignature))\n\t\tcase KIND_NAMED_TYPE:\n\t\t\terr = unmarshalArguments(&(signature.Arguments[i].namedTypeSignature.TypeSignature))\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (qr *driverRows) initColumns(qresp *queryResponse) error {\n\tif qr.columns != nil || len(qresp.Columns) == 0 {\n\t\treturn nil\n\t}\n\tvar err error\n\tfor i := range qresp.Columns {\n\t\terr = unmarshalArguments(&(qresp.Columns[i].TypeSignature))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error decoding column type signature: %w\", err)\n\t\t}\n\t}\n\tqr.columns = make([]string, len(qresp.Columns))\n\tqr.coltype = make([]*typeConverter, len(qresp.Columns))\n\tfor i, col := range qresp.Columns {\n\t\terr = unmarshalArguments(&(qresp.Columns[i].TypeSignature))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error decoding column type signature: %w\", err)\n\t\t}\n\t\tqr.columns[i] = col.Name\n\t\tqr.coltype[i], err = newTypeConverter(col.Type, col.TypeSignature)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (qr *driverRows) scheduleProgressUpdate(id string, stats stmtStats) {\n\tif qr.stmt.conn.progressUpdater == nil {\n\t\treturn\n\t}\n\n\tqrStats := QueryProgressInfo{\n\t\tQueryId:    id,\n\t\tQueryStats: stats,\n\t}\n\tcurrentTime := time.Now()\n\tdiff := currentTime.Sub(qr.stmt.conn.progressUpdaterPeriod.LastCallbackTime)\n\tperiod := qr.stmt.conn.progressUpdaterPeriod.Period\n\n\t// Check if period has not passed yet AND if query state did not change\n\tif diff < period && qr.stmt.conn.progressUpdaterPeriod.LastQueryState == qrStats.QueryStats.State {\n\t\treturn\n\t}\n\n\tselect {\n\tcase qr.statsCh <- qrStats:\n\tdefault:\n\t\t// ignore when can't send stats\n\t}\n\tqr.stmt.conn.progressUpdaterPeriod.LastCallbackTime = currentTime\n\tqr.stmt.conn.progressUpdaterPeriod.LastQueryState = qrStats.QueryStats.State\n}\n\ntype typeConverter struct {\n\ttypeName   string\n\tparsedType []string\n\tscanType   reflect.Type\n\tprecision  optionalInt64\n\tscale      optionalInt64\n\tsize       optionalInt64\n}\n\ntype optionalInt64 struct {\n\tvalue    int64\n\thasValue bool\n}\n\nfunc newOptionalInt64(value int64) optionalInt64 {\n\treturn optionalInt64{value: value, hasValue: true}\n}\n\nfunc newTypeConverter(typeName string, signature typeSignature) (*typeConverter, error) {\n\tresult := &typeConverter{\n\t\ttypeName:   typeName,\n\t\tparsedType: getNestedTypes([]string{}, signature),\n\t}\n\tvar err error\n\tresult.scanType, err = getScanType(result.parsedType)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch signature.RawType {\n\tcase \"char\", \"varchar\":\n\t\tif len(signature.Arguments) > 0 {\n\t\t\tif signature.Arguments[0].Kind != KIND_LONG {\n\t\t\t\treturn nil, ErrInvalidResponseType\n\t\t\t}\n\t\t\tresult.size = newOptionalInt64(signature.Arguments[0].long)\n\t\t}\n\tcase \"decimal\":\n\t\tif len(signature.Arguments) > 0 {\n\t\t\tif signature.Arguments[0].Kind != KIND_LONG {\n\t\t\t\treturn nil, ErrInvalidResponseType\n\t\t\t}\n\t\t\tresult.precision = newOptionalInt64(signature.Arguments[0].long)\n\t\t}\n\t\tif len(signature.Arguments) > 1 {\n\t\t\tif signature.Arguments[1].Kind != KIND_LONG {\n\t\t\t\treturn nil, ErrInvalidResponseType\n\t\t\t}\n\t\t\tresult.scale = newOptionalInt64(signature.Arguments[1].long)\n\t\t}\n\tcase \"time\", \"time with time zone\", \"timestamp\", \"timestamp with time zone\":\n\t\tif len(signature.Arguments) > 0 {\n\t\t\tif signature.Arguments[0].Kind != KIND_LONG {\n\t\t\t\treturn nil, ErrInvalidResponseType\n\t\t\t}\n\t\t\tresult.precision = newOptionalInt64(signature.Arguments[0].long)\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\nfunc getNestedTypes(types []string, signature typeSignature) []string {\n\ttypes = append(types, signature.RawType)\n\tif len(signature.Arguments) == 1 {\n\t\tswitch signature.Arguments[0].Kind {\n\t\tcase KIND_TYPE:\n\t\t\ttypes = getNestedTypes(types, signature.Arguments[0].typeSignature)\n\t\tcase KIND_NAMED_TYPE:\n\t\t\ttypes = getNestedTypes(types, signature.Arguments[0].namedTypeSignature.TypeSignature)\n\t\t}\n\t}\n\treturn types\n}\n\nfunc getScanType(typeNames []string) (reflect.Type, error) {\n\tvar v interface{}\n\tswitch typeNames[0] {\n\tcase \"boolean\":\n\t\tv = sql.NullBool{}\n\tcase \"json\", \"char\", \"varchar\", \"interval year to month\", \"interval day to second\", \"decimal\", \"ipaddress\", \"uuid\", \"unknown\":\n\t\tv = sql.NullString{}\n\tcase \"varbinary\":\n\t\tv = []byte{}\n\tcase \"tinyint\", \"smallint\":\n\t\tv = sql.NullInt32{}\n\tcase \"integer\":\n\t\tv = sql.NullInt32{}\n\tcase \"bigint\":\n\t\tv = sql.NullInt64{}\n\tcase \"real\", \"double\":\n\t\tv = sql.NullFloat64{}\n\tcase \"date\", \"time\", \"time with time zone\", \"timestamp\", \"timestamp with time zone\":\n\t\tv = sql.NullTime{}\n\tcase \"map\":\n\t\tv = NullMap{}\n\tcase \"array\":\n\t\tif len(typeNames) <= 1 {\n\t\t\treturn nil, ErrInvalidResponseType\n\t\t}\n\t\tswitch typeNames[1] {\n\t\tcase \"boolean\":\n\t\t\tv = NullSliceBool{}\n\t\tcase \"json\", \"char\", \"varchar\", \"varbinary\", \"interval year to month\", \"interval day to second\", \"decimal\", \"ipaddress\", \"uuid\", \"unknown\":\n\t\t\tv = NullSliceString{}\n\t\tcase \"tinyint\", \"smallint\", \"integer\", \"bigint\":\n\t\t\tv = NullSliceInt64{}\n\t\tcase \"real\", \"double\":\n\t\t\tv = NullSliceFloat64{}\n\t\tcase \"date\", \"time\", \"time with time zone\", \"timestamp\", \"timestamp with time zone\":\n\t\t\tv = NullSliceTime{}\n\t\tcase \"map\":\n\t\t\tv = NullSliceMap{}\n\t\tcase \"array\":\n\t\t\tif len(typeNames) <= 2 {\n\t\t\t\treturn nil, ErrInvalidResponseType\n\t\t\t}\n\t\t\tswitch typeNames[2] {\n\t\t\tcase \"boolean\":\n\t\t\t\tv = NullSlice2Bool{}\n\t\t\tcase \"json\", \"char\", \"varchar\", \"varbinary\", \"interval year to month\", \"interval day to second\", \"decimal\", \"ipaddress\", \"uuid\", \"unknown\":\n\t\t\t\tv = NullSlice2String{}\n\t\t\tcase \"tinyint\", \"smallint\", \"integer\", \"bigint\":\n\t\t\t\tv = NullSlice2Int64{}\n\t\t\tcase \"real\", \"double\":\n\t\t\t\tv = NullSlice2Float64{}\n\t\t\tcase \"date\", \"time\", \"time with time zone\", \"timestamp\", \"timestamp with time zone\":\n\t\t\t\tv = NullSlice2Time{}\n\t\t\tcase \"map\":\n\t\t\t\tv = NullSlice2Map{}\n\t\t\tcase \"array\":\n\t\t\t\tif len(typeNames) <= 3 {\n\t\t\t\t\treturn nil, ErrInvalidResponseType\n\t\t\t\t}\n\t\t\t\tswitch typeNames[3] {\n\t\t\t\tcase \"boolean\":\n\t\t\t\t\tv = NullSlice3Bool{}\n\t\t\t\tcase \"json\", \"char\", \"varchar\", \"varbinary\", \"interval year to month\", \"interval day to second\", \"decimal\", \"ipaddress\", \"uuid\", \"unknown\":\n\t\t\t\t\tv = NullSlice3String{}\n\t\t\t\tcase \"tinyint\", \"smallint\", \"integer\", \"bigint\":\n\t\t\t\t\tv = NullSlice3Int64{}\n\t\t\t\tcase \"real\", \"double\":\n\t\t\t\t\tv = NullSlice3Float64{}\n\t\t\t\tcase \"date\", \"time\", \"time with time zone\", \"timestamp\", \"timestamp with time zone\":\n\t\t\t\t\tv = NullSlice3Time{}\n\t\t\t\tcase \"map\":\n\t\t\t\t\tv = NullSlice3Map{}\n\t\t\t\t}\n\t\t\t\t// if this is a 4 or more dimensional array, scan type will be an empty interface\n\t\t\t}\n\t\t}\n\t}\n\tif v == nil {\n\t\treturn reflect.TypeOf(new(interface{})).Elem(), nil\n\t}\n\treturn reflect.TypeOf(v), nil\n}\n\n// ConvertValue implements the driver.ValueConverter interface.\nfunc (c *typeConverter) ConvertValue(v interface{}) (driver.Value, error) {\n\tswitch c.parsedType[0] {\n\tcase \"boolean\":\n\t\tvv, err := scanNullBool(v)\n\t\tif !vv.Valid {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn vv.Bool, err\n\tcase \"json\", \"char\", \"varchar\", \"interval year to month\", \"interval day to second\", \"decimal\", \"ipaddress\", \"uuid\", \"Geometry\", \"SphericalGeography\", \"unknown\":\n\t\tvv, err := scanNullString(v)\n\t\tif !vv.Valid {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn vv.String, err\n\tcase \"varbinary\":\n\t\tvv, err := scanNullBytes(v)\n\t\tif !vv.Valid {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn vv.Bytes, err\n\tcase \"tinyint\", \"smallint\", \"integer\", \"bigint\":\n\t\tvv, err := scanNullInt64(v)\n\t\tif !vv.Valid {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn vv.Int64, err\n\tcase \"real\", \"double\":\n\t\tvv, err := scanNullFloat64(v)\n\t\tif !vv.Valid {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn vv.Float64, err\n\tcase \"date\", \"time\", \"time with time zone\", \"timestamp\", \"timestamp with time zone\":\n\t\tvv, err := scanNullTime(v)\n\t\tif !vv.Valid {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn vv.Time, err\n\tcase \"map\":\n\t\tif err := validateMap(v); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn v, nil\n\tcase \"array\":\n\t\tif err := validateSlice(v); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn v, nil\n\tcase \"row\":\n\t\tif err := validateSlice(v); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn v, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"type not supported: %q\", c.typeName)\n\t}\n}\n\nfunc validateMap(v interface{}) error {\n\tif v == nil {\n\t\treturn nil\n\t}\n\tif _, ok := v.(map[string]interface{}); !ok {\n\t\treturn fmt.Errorf(\"cannot convert %v (%T) to map\", v, v)\n\t}\n\treturn nil\n}\n\nfunc validateSlice(v interface{}) error {\n\tif v == nil {\n\t\treturn nil\n\t}\n\tif _, ok := v.([]interface{}); !ok {\n\t\treturn fmt.Errorf(\"cannot convert %v (%T) to slice\", v, v)\n\t}\n\treturn nil\n}\n\nfunc scanNullBool(v interface{}) (sql.NullBool, error) {\n\tif v == nil {\n\t\treturn sql.NullBool{}, nil\n\t}\n\tvv, ok := v.(bool)\n\tif !ok {\n\t\treturn sql.NullBool{},\n\t\t\tfmt.Errorf(\"cannot convert %v (%T) to bool\", v, v)\n\t}\n\treturn sql.NullBool{Valid: true, Bool: vv}, nil\n}\n\n// NullSliceBool represents a slice of bool that may be null.\ntype NullSliceBool struct {\n\tSliceBool []sql.NullBool\n\tValid     bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSliceBool) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.SliceBool, s.Valid = []sql.NullBool{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to []bool\", value, value)\n\t}\n\tslice := make([]sql.NullBool, len(vs))\n\tfor i := range vs {\n\t\tv, err := scanNullBool(vs[i])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = v\n\t}\n\ts.SliceBool = slice\n\ts.Valid = true\n\treturn nil\n}\n\n// NullSlice2Bool represents a two-dimensional slice of bool that may be null.\ntype NullSlice2Bool struct {\n\tSlice2Bool [][]sql.NullBool\n\tValid      bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSlice2Bool) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.Slice2Bool, s.Valid = [][]sql.NullBool{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to [][]bool\", value, value)\n\t}\n\tslice := make([][]sql.NullBool, len(vs))\n\tfor i := range vs {\n\t\tvar ss NullSliceBool\n\t\tif err := ss.Scan(vs[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = ss.SliceBool\n\t}\n\ts.Slice2Bool = slice\n\ts.Valid = true\n\treturn nil\n}\n\n// NullSlice3Bool implements a three-dimensional slice of bool that may be null.\ntype NullSlice3Bool struct {\n\tSlice3Bool [][][]sql.NullBool\n\tValid      bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSlice3Bool) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.Slice3Bool, s.Valid = [][][]sql.NullBool{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to [][][]bool\", value, value)\n\t}\n\tslice := make([][][]sql.NullBool, len(vs))\n\tfor i := range vs {\n\t\tvar ss NullSlice2Bool\n\t\tif err := ss.Scan(vs[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = ss.Slice2Bool\n\t}\n\ts.Slice3Bool = slice\n\ts.Valid = true\n\treturn nil\n}\n\nfunc scanNullString(v interface{}) (sql.NullString, error) {\n\tif v == nil {\n\t\treturn sql.NullString{}, nil\n\t}\n\tvv, ok := v.(string)\n\tif !ok {\n\t\treturn sql.NullString{},\n\t\t\tfmt.Errorf(\"cannot convert %v (%T) to string\", v, v)\n\t}\n\treturn sql.NullString{Valid: true, String: vv}, nil\n}\n\n// NullBinary represents a []byte that may be null.\n// This follows the same pattern as sql.NullString, sql.NullInt64, etc.\ntype NullBinary struct {\n\tBytes []byte\n\tValid bool // Valid is true if Bytes is not NULL\n}\n\nfunc scanNullBytes(v interface{}) (NullBinary, error) {\n\tif v == nil {\n\t\treturn NullBinary{}, nil // Valid: false, Bytes: nil\n\t}\n\n\t// VARBINARY values come back as a base64 encoded string.\n\tvv, ok := v.(string)\n\tif !ok {\n\t\treturn NullBinary{}, fmt.Errorf(\"cannot convert %v (%T) to []byte\", v, v)\n\t}\n\n\t// Decode the base64 encoded string into a []byte.\n\tdecoded, err := base64.StdEncoding.DecodeString(vv)\n\tif err != nil {\n\t\treturn NullBinary{}, fmt.Errorf(\"cannot decode base64 string into []byte: %w\", err)\n\t}\n\n\treturn NullBinary{Bytes: decoded, Valid: true}, nil\n}\n\n// NullSliceString represents a slice of string that may be null.\ntype NullSliceString struct {\n\tSliceString []sql.NullString\n\tValid       bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSliceString) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.SliceString, s.Valid = []sql.NullString{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to []string\", value, value)\n\t}\n\tslice := make([]sql.NullString, len(vs))\n\tfor i := range vs {\n\t\tv, err := scanNullString(vs[i])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = v\n\t}\n\ts.SliceString = slice\n\ts.Valid = true\n\treturn nil\n}\n\n// NullSlice2String represents a two-dimensional slice of string that may be null.\ntype NullSlice2String struct {\n\tSlice2String [][]sql.NullString\n\tValid        bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSlice2String) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.Slice2String, s.Valid = [][]sql.NullString{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to [][]string\", value, value)\n\t}\n\tslice := make([][]sql.NullString, len(vs))\n\tfor i := range vs {\n\t\tvar ss NullSliceString\n\t\tif err := ss.Scan(vs[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = ss.SliceString\n\t}\n\ts.Slice2String = slice\n\ts.Valid = true\n\treturn nil\n}\n\n// NullSlice3String implements a three-dimensional slice of string that may be null.\ntype NullSlice3String struct {\n\tSlice3String [][][]sql.NullString\n\tValid        bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSlice3String) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.Slice3String, s.Valid = [][][]sql.NullString{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to [][][]string\", value, value)\n\t}\n\tslice := make([][][]sql.NullString, len(vs))\n\tfor i := range vs {\n\t\tvar ss NullSlice2String\n\t\tif err := ss.Scan(vs[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = ss.Slice2String\n\t}\n\ts.Slice3String = slice\n\ts.Valid = true\n\treturn nil\n}\n\nfunc scanNullInt64(v interface{}) (sql.NullInt64, error) {\n\tif v == nil {\n\t\treturn sql.NullInt64{}, nil\n\t}\n\tvNumber, ok := v.(json.Number)\n\tif !ok {\n\t\treturn sql.NullInt64{},\n\t\t\tfmt.Errorf(\"cannot convert %v (%T) to int64\", v, v)\n\t}\n\tvv, err := vNumber.Int64()\n\tif err != nil {\n\t\treturn sql.NullInt64{},\n\t\t\tfmt.Errorf(\"cannot convert %v (%T) to int64\", v, v)\n\t}\n\treturn sql.NullInt64{Valid: true, Int64: vv}, nil\n}\n\n// NullSliceInt64 represents a slice of int64 that may be null.\ntype NullSliceInt64 struct {\n\tSliceInt64 []sql.NullInt64\n\tValid      bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSliceInt64) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.SliceInt64, s.Valid = []sql.NullInt64{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to []int64\", value, value)\n\t}\n\tslice := make([]sql.NullInt64, len(vs))\n\tfor i := range vs {\n\t\tv, err := scanNullInt64(vs[i])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = v\n\t}\n\ts.SliceInt64 = slice\n\ts.Valid = true\n\treturn nil\n}\n\n// NullSlice2Int64 represents a two-dimensional slice of int64 that may be null.\ntype NullSlice2Int64 struct {\n\tSlice2Int64 [][]sql.NullInt64\n\tValid       bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSlice2Int64) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.Slice2Int64, s.Valid = [][]sql.NullInt64{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to [][]int64\", value, value)\n\t}\n\tslice := make([][]sql.NullInt64, len(vs))\n\tfor i := range vs {\n\t\tvar ss NullSliceInt64\n\t\tif err := ss.Scan(vs[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = ss.SliceInt64\n\t}\n\ts.Slice2Int64 = slice\n\ts.Valid = true\n\treturn nil\n}\n\n// NullSlice3Int64 implements a three-dimensional slice of int64 that may be null.\ntype NullSlice3Int64 struct {\n\tSlice3Int64 [][][]sql.NullInt64\n\tValid       bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSlice3Int64) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.Slice3Int64, s.Valid = [][][]sql.NullInt64{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to [][][]int64\", value, value)\n\t}\n\tslice := make([][][]sql.NullInt64, len(vs))\n\tfor i := range vs {\n\t\tvar ss NullSlice2Int64\n\t\tif err := ss.Scan(vs[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = ss.Slice2Int64\n\t}\n\ts.Slice3Int64 = slice\n\ts.Valid = true\n\treturn nil\n}\n\nfunc scanNullFloat64(v interface{}) (sql.NullFloat64, error) {\n\tif v == nil {\n\t\treturn sql.NullFloat64{}, nil\n\t}\n\tvNumber, ok := v.(json.Number)\n\tif ok {\n\t\tvFloat, err := vNumber.Float64()\n\t\tif err != nil {\n\t\t\treturn sql.NullFloat64{}, fmt.Errorf(\"cannot convert %v (%T) to float64: %w\", vNumber, vNumber, err)\n\t\t}\n\t\treturn sql.NullFloat64{Valid: true, Float64: vFloat}, nil\n\t}\n\tswitch v {\n\tcase \"NaN\":\n\t\treturn sql.NullFloat64{Valid: true, Float64: math.NaN()}, nil\n\tcase \"Infinity\":\n\t\treturn sql.NullFloat64{Valid: true, Float64: math.Inf(+1)}, nil\n\tcase \"-Infinity\":\n\t\treturn sql.NullFloat64{Valid: true, Float64: math.Inf(-1)}, nil\n\tdefault:\n\t\tvString, ok := v.(string)\n\t\tif !ok {\n\t\t\treturn sql.NullFloat64{}, fmt.Errorf(\"cannot convert %v (%T) to float64\", v, v)\n\t\t}\n\t\tvFloat, err := strconv.ParseFloat(vString, 64)\n\t\tif err != nil {\n\t\t\treturn sql.NullFloat64{}, fmt.Errorf(\"cannot convert %v (%T) to float64: %w\", v, v, err)\n\t\t}\n\t\treturn sql.NullFloat64{Valid: true, Float64: vFloat}, nil\n\t}\n}\n\n// NullSliceFloat64 represents a slice of float64 that may be null.\ntype NullSliceFloat64 struct {\n\tSliceFloat64 []sql.NullFloat64\n\tValid        bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSliceFloat64) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.SliceFloat64, s.Valid = []sql.NullFloat64{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to []float64\", value, value)\n\t}\n\tslice := make([]sql.NullFloat64, len(vs))\n\tfor i := range vs {\n\t\tv, err := scanNullFloat64(vs[i])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = v\n\t}\n\ts.SliceFloat64 = slice\n\ts.Valid = true\n\treturn nil\n}\n\n// NullSlice2Float64 represents a two-dimensional slice of float64 that may be null.\ntype NullSlice2Float64 struct {\n\tSlice2Float64 [][]sql.NullFloat64\n\tValid         bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSlice2Float64) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.Slice2Float64, s.Valid = [][]sql.NullFloat64{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to [][]float64\", value, value)\n\t}\n\tslice := make([][]sql.NullFloat64, len(vs))\n\tfor i := range vs {\n\t\tvar ss NullSliceFloat64\n\t\tif err := ss.Scan(vs[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = ss.SliceFloat64\n\t}\n\ts.Slice2Float64 = slice\n\ts.Valid = true\n\treturn nil\n}\n\n// NullSlice3Float64 represents a three-dimensional slice of float64 that may be null.\ntype NullSlice3Float64 struct {\n\tSlice3Float64 [][][]sql.NullFloat64\n\tValid         bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSlice3Float64) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.Slice3Float64, s.Valid = [][][]sql.NullFloat64{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to [][][]float64\", value, value)\n\t}\n\tslice := make([][][]sql.NullFloat64, len(vs))\n\tfor i := range vs {\n\t\tvar ss NullSlice2Float64\n\t\tif err := ss.Scan(vs[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = ss.Slice2Float64\n\t}\n\ts.Slice3Float64 = slice\n\ts.Valid = true\n\treturn nil\n}\n\n// Layout for time and timestamp WITHOUT time zone.\n// Trino can support up to 12 digits sub second precision, but Go only 9.\n// (Requires X-Trino-Client-Capabilities: PARAMETRIC_DATETIME)\nvar timeLayouts = []string{\n\t\"2006-01-02\",\n\t\"15:04:05.999999999\",\n\t\"2006-01-02 15:04:05.999999999\",\n}\n\n// Layout for time and timestamp WITH time zone.\n// Trino can support up to 12 digits sub second precision, but Go only 9.\n// (Requires X-Trino-Client-Capabilities: PARAMETRIC_DATETIME)\nvar timeLayoutsTZ = []string{\n\t\"15:04:05.999999999 -07:00\",\n\t\"2006-01-02 15:04:05.999999999 -07:00\",\n}\n\nfunc scanNullTime(v interface{}) (NullTime, error) {\n\tif v == nil {\n\t\treturn NullTime{}, nil\n\t}\n\tvv, ok := v.(string)\n\tif !ok {\n\t\treturn NullTime{}, fmt.Errorf(\"cannot convert %v (%T) to time string\", v, v)\n\t}\n\tvparts := strings.Split(vv, \" \")\n\tif len(vparts) > 1 && !unicode.IsDigit(rune(vparts[len(vparts)-1][0])) {\n\t\treturn parseNullTimeWithLocation(vv)\n\t}\n\t// Time literals may not have spaces before the timezone.\n\tif strings.ContainsRune(vv, '+') {\n\t\treturn parseNullTimeWithLocation(strings.Replace(vv, \"+\", \" +\", 1))\n\t}\n\thyphenCount := strings.Count(vv, \"-\")\n\t// We need to ensure we don't treat the hyphens in dates as the minus offset sign.\n\t// So if there's only one hyphen or more than 2, we have a negative offset.\n\tif hyphenCount == 1 || hyphenCount > 2 {\n\t\t// We add a space before the last hyphen to parse properly.\n\t\ti := strings.LastIndex(vv, \"-\")\n\t\ttimestamp := vv[:i] + strings.Replace(vv[i:], \"-\", \" -\", 1)\n\t\treturn parseNullTimeWithLocation(timestamp)\n\t}\n\treturn parseNullTime(vv)\n}\n\nfunc parseNullTime(v string) (NullTime, error) {\n\tvar t time.Time\n\tvar err error\n\tfor _, layout := range timeLayouts {\n\t\tt, err = time.ParseInLocation(layout, v, time.Local)\n\t\tif err == nil {\n\t\t\treturn NullTime{Valid: true, Time: t}, nil\n\t\t}\n\t}\n\treturn NullTime{}, err\n}\n\nfunc parseNullTimeWithLocation(v string) (NullTime, error) {\n\tidx := strings.LastIndex(v, \" \")\n\tif idx == -1 {\n\t\treturn NullTime{}, fmt.Errorf(\"cannot convert %v (%T) to time+zone\", v, v)\n\t}\n\tstamp, location := v[:idx], v[idx+1:]\n\tvar t time.Time\n\tvar err error\n\t// Try offset timezones.\n\tif strings.HasPrefix(location, \"+\") || strings.HasPrefix(location, \"-\") {\n\t\tfor _, layout := range timeLayoutsTZ {\n\t\t\tt, err = time.Parse(layout, v)\n\t\t\tif err == nil {\n\t\t\t\treturn NullTime{Valid: true, Time: t}, nil\n\t\t\t}\n\t\t}\n\t\treturn NullTime{}, err\n\t}\n\tloc, err := time.LoadLocation(location)\n\t// Not a named location.\n\tif err != nil {\n\t\treturn NullTime{}, fmt.Errorf(\"cannot load timezone %q: %v\", location, err)\n\t}\n\n\tfor _, layout := range timeLayouts {\n\t\tt, err = time.ParseInLocation(layout, stamp, loc)\n\t\tif err == nil {\n\t\t\treturn NullTime{Valid: true, Time: t}, nil\n\t\t}\n\t}\n\treturn NullTime{}, err\n}\n\n// NullTime represents a time.Time value that can be null.\n// The NullTime supports Trino's Date, Time and Timestamp data types,\n// with or without time zone.\ntype NullTime struct {\n\tTime  time.Time\n\tValid bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullTime) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.Time, s.Valid = time.Time{}, false\n\t\treturn nil\n\t}\n\tswitch t := value.(type) {\n\tcase time.Time:\n\t\ts.Time, s.Valid = t, true\n\tcase NullTime:\n\t\t*s = t\n\t}\n\treturn nil\n}\n\n// NullSliceTime represents a slice of time.Time that may be null.\ntype NullSliceTime struct {\n\tSliceTime []NullTime\n\tValid     bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSliceTime) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.SliceTime, s.Valid = []NullTime{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to []time.Time\", value, value)\n\t}\n\tslice := make([]NullTime, len(vs))\n\tfor i := range vs {\n\t\tv, err := scanNullTime(vs[i])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = v\n\t}\n\ts.SliceTime = slice\n\ts.Valid = true\n\treturn nil\n}\n\n// NullSlice2Time represents a two-dimensional slice of time.Time that may be null.\ntype NullSlice2Time struct {\n\tSlice2Time [][]NullTime\n\tValid      bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSlice2Time) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.Slice2Time, s.Valid = [][]NullTime{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to [][]time.Time\", value, value)\n\t}\n\tslice := make([][]NullTime, len(vs))\n\tfor i := range vs {\n\t\tvar ss NullSliceTime\n\t\tif err := ss.Scan(vs[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = ss.SliceTime\n\t}\n\ts.Slice2Time = slice\n\ts.Valid = true\n\treturn nil\n}\n\n// NullSlice3Time represents a three-dimensional slice of time.Time that may be null.\ntype NullSlice3Time struct {\n\tSlice3Time [][][]NullTime\n\tValid      bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSlice3Time) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.Slice3Time, s.Valid = [][][]NullTime{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to [][][]time.Time\", value, value)\n\t}\n\tslice := make([][][]NullTime, len(vs))\n\tfor i := range vs {\n\t\tvar ss NullSlice2Time\n\t\tif err := ss.Scan(vs[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = ss.Slice2Time\n\t}\n\ts.Slice3Time = slice\n\ts.Valid = true\n\treturn nil\n}\n\n// NullMap represents a map type that may be null.\ntype NullMap struct {\n\tMap   map[string]interface{}\n\tValid bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (m *NullMap) Scan(v interface{}) error {\n\tif v == nil {\n\t\tm.Map, m.Valid = map[string]interface{}{}, false\n\t\treturn nil\n\t}\n\tm.Map, m.Valid = v.(map[string]interface{})\n\treturn nil\n}\n\n// NullSliceMap represents a slice of NullMap that may be null.\ntype NullSliceMap struct {\n\tSliceMap []NullMap\n\tValid    bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSliceMap) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.SliceMap, s.Valid = []NullMap{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to []NullMap\", value, value)\n\t}\n\tslice := make([]NullMap, len(vs))\n\tfor i := range vs {\n\t\tif err := validateMap(vs[i]); err != nil {\n\t\t\treturn fmt.Errorf(\"cannot convert %v (%T) to []NullMap\", value, value)\n\t\t}\n\t\tm := NullMap{}\n\t\t// this scan can never fail\n\t\t_ = m.Scan(vs[i])\n\t\tslice[i] = m\n\t}\n\ts.SliceMap = slice\n\ts.Valid = true\n\treturn nil\n}\n\n// NullSlice2Map represents a two-dimensional slice of NullMap that may be null.\ntype NullSlice2Map struct {\n\tSlice2Map [][]NullMap\n\tValid     bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSlice2Map) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.Slice2Map, s.Valid = [][]NullMap{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to [][]NullMap\", value, value)\n\t}\n\tslice := make([][]NullMap, len(vs))\n\tfor i := range vs {\n\t\tvar ss NullSliceMap\n\t\tif err := ss.Scan(vs[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = ss.SliceMap\n\t}\n\ts.Slice2Map = slice\n\ts.Valid = true\n\treturn nil\n}\n\n// NullSlice3Map represents a three-dimensional slice of NullMap that may be null.\ntype NullSlice3Map struct {\n\tSlice3Map [][][]NullMap\n\tValid     bool\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (s *NullSlice3Map) Scan(value interface{}) error {\n\tif value == nil {\n\t\ts.Slice3Map, s.Valid = [][][]NullMap{}, false\n\t\treturn nil\n\t}\n\tvs, ok := value.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"trino: cannot convert %v (%T) to [][][]NullMap\", value, value)\n\t}\n\tslice := make([][][]NullMap, len(vs))\n\tfor i := range vs {\n\t\tvar ss NullSlice2Map\n\t\tif err := ss.Scan(vs[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tslice[i] = ss.Slice2Map\n\t}\n\ts.Slice3Map = slice\n\ts.Valid = true\n\treturn nil\n}\n\ntype QueryProgressInfo struct {\n\tQueryId    string\n\tQueryStats stmtStats\n}\n\ntype queryProgressCallbackPeriod struct {\n\tPeriod           time.Duration\n\tLastCallbackTime time.Time\n\tLastQueryState   string\n}\n\ntype ProgressUpdater interface {\n\t// Update the query progress, immediately when the query starts, when receiving data, and once when the query is finished.\n\tUpdate(QueryProgressInfo)\n}\n"
  },
  {
    "path": "trino/trino_test.go",
    "content": "// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage trino\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"database/sql\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"runtime/debug\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestConfig(t *testing.T) {\n\tc := &Config{\n\t\tServerURI:         \"http://foobar@localhost:8080\",\n\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\twant := \"http://foobar@localhost:8080?session_properties=query_priority%3A1&source=trino-go-client\"\n\n\tassert.Equal(t, want, dsn)\n}\n\nfunc TestPreserveExplicitPrepareQueryParameterConfig(t *testing.T) {\n\tc := &Config{\n\t\tServerURI:              \"https://foobar@localhost:8090\",\n\t\tDisableExplicitPrepare: true,\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\twant := \"https://foobar@localhost:8090?explicitPrepare=false&source=trino-go-client\"\n\n\tassert.Equal(t, want, dsn)\n}\n\nfunc TestParseDSNToConfig(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\tconfig *Config\n\t}{\n\t\t{\n\t\t\tname: \"HTTP with custom client and full configuration\",\n\t\t\tconfig: &Config{\n\t\t\t\tServerURI:                  \"http://foobar@localhost:8080\",\n\t\t\t\tSource:                     \"trino-go-client\",\n\t\t\t\tCatalog:                    \"test_catalog\",\n\t\t\t\tSchema:                     \"test_schema\",\n\t\t\t\tSessionProperties:          map[string]string{\"session_property_one\": \"1\", \"session_property_two\": \"2\"},\n\t\t\t\tExtraCredentials:           map[string]string{\"extra_credential_one\": \"1\", \"extra_credential_two\": \"2\"},\n\t\t\t\tClientTags:                 []string{\"tag1\", \"tag2\", \"tag3\"},\n\t\t\t\tCustomClientName:           \"client_name\",\n\t\t\t\tAccessToken:                \"token_test\",\n\t\t\t\tDisableExplicitPrepare:     true,\n\t\t\t\tForwardAuthorizationHeader: true,\n\t\t\t\tQueryTimeout:               &[]time.Duration{5 * time.Minute}[0],\n\t\t\t\tRoles:                      map[string]string{\"catalog1\": \"role1\", \"catalog2\": \"role2\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"HTTPS with Kerberos and SSL cert path\",\n\t\t\tconfig: &Config{\n\t\t\t\tServerURI:                  \"https://foobar@localhost:8080\",\n\t\t\t\tSource:                     \"trino-go-client\",\n\t\t\t\tCatalog:                    \"test_catalog\",\n\t\t\t\tSchema:                     \"test_schema\",\n\t\t\t\tSessionProperties:          map[string]string{\"session_property_one\": \"1\", \"session_property_two\": \"2\"},\n\t\t\t\tExtraCredentials:           map[string]string{\"extra_credential_one\": \"1\", \"extra_credential_two\": \"2\"},\n\t\t\t\tClientTags:                 []string{\"tag1\", \"tag2\", \"tag3\"},\n\t\t\t\tKerberosEnabled:            true, // Requires HTTPS\n\t\t\t\tKerberosKeytabPath:         \"kerberos-path\",\n\t\t\t\tKerberosPrincipal:          \"kerberos-pricipal\",\n\t\t\t\tKerberosRemoteServiceName:  \"kerberos-remote-service-name\",\n\t\t\t\tKerberosRealm:              \"kerberos-realm\",\n\t\t\t\tKerberosConfigPath:         \"kerberos-config-path\",\n\t\t\t\tSSLCertPath:                \"ssl-cert-path\",\n\t\t\t\tAccessToken:                \"token_test\",\n\t\t\t\tDisableExplicitPrepare:     true,\n\t\t\t\tForwardAuthorizationHeader: true,\n\t\t\t\tQueryTimeout:               &[]time.Duration{5 * time.Minute}[0],\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"HTTPS with SSL cert string (alternative to cert path)\",\n\t\t\tconfig: &Config{\n\t\t\t\tServerURI: \"https://localhost:8080\",\n\t\t\t\tSource:    \"trino-go-client\",\n\t\t\t\tSSLCert:   \"-----BEGIN CERTIFICATE-----\\ntest-cert-data\\n-----END CERTIFICATE-----\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP with explicit default boolean values\",\n\t\t\tconfig: &Config{\n\t\t\t\tServerURI:                  \"http://localhost:8080\",\n\t\t\t\tSource:                     \"trino-go-client\",\n\t\t\t\tDisableExplicitPrepare:     false,\n\t\t\t\tForwardAuthorizationHeader: false,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdsn, err := tt.config.FormatDSN()\n\t\t\trequire.NoError(t, err)\n\t\t\tgot, err := ParseDSN(dsn)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, tt.config, got)\n\t\t})\n\t}\n}\n\nfunc TestParseDSNToConfigAllFieldsHandled(t *testing.T) {\n\tcomplexDSN := \"https://user:pass@localhost:8080/?\" +\n\t\t\"source=test-source&\" +\n\t\t\"catalog=test_catalog&\" +\n\t\t\"schema=test_schema&\" +\n\t\t\"session_properties=prop1%3Avalue1%3Bprop2%3Avalue2&\" +\n\t\t\"extra_credentials=cred1%3Asecret1%3Bcred2%3Asecret2&\" +\n\t\t\"clientTags=tag1%2Ctag2%2Ctag3&\" +\n\t\t\"custom_client=test_client&\" +\n\t\t\"KerberosEnabled=true&\" +\n\t\t\"KerberosKeytabPath=/path/to/keytab&\" +\n\t\t\"KerberosPrincipal=user%40REALM.COM&\" +\n\t\t\"KerberosRemoteServiceName=trino-service&\" +\n\t\t\"KerberosRealm=REALM.COM&\" +\n\t\t\"KerberosConfigPath=/etc/krb5.conf&\" +\n\t\t\"SSLCertPath=/path/to/cert.pem&\" +\n\t\t\"SSLCert=-----BEGIN%20CERTIFICATE-----test-cert-----END%20CERTIFICATE-----&\" +\n\t\t\"accessToken=jwt-token-here&\" +\n\t\t\"explicitPrepare=false&\" +\n\t\t\"forwardAuthorizationHeader=true&\" +\n\t\t\"query_timeout=5m30s&\" +\n\t\t\"roles=catalog1%3Arole1%3Bcatalog2%3Arole2\"\n\n\tconfig, err := ParseDSN(complexDSN)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, config)\n\n\tv := reflect.ValueOf(config).Elem()\n\tconfigType := v.Type()\n\n\tfor i := 0; i < v.NumField(); i++ {\n\t\tfield := v.Field(i)\n\t\tfieldName := configType.Field(i).Name\n\t\tfieldType := field.Type()\n\n\t\tswitch fieldType.Kind() {\n\t\tcase reflect.String:\n\t\t\tassert.NotEmpty(t, field.String(), \"Field %s should not be empty - add it to the test DSN and ParseDSNToConfig\", fieldName)\n\t\tcase reflect.Slice:\n\t\t\tassert.Greater(t, field.Len(), 0, \"Field %s should not be empty slice - add it to the test DSN and ParseDSNToConfig\", fieldName)\n\t\tcase reflect.Map:\n\t\t\tassert.Greater(t, field.Len(), 0, \"Field %s should not be empty map - add it to the test DSN and ParseDSNToConfig\", fieldName)\n\t\tcase reflect.Bool:\n\t\t\tassert.True(t, field.Bool(), \"Field %s should be true - add it to the test DSN and ParseDSNToConfig\", fieldName)\n\t\tcase reflect.Ptr:\n\t\t\tassert.NotNil(t, field.Interface(), \"Field %s should not be nil - add it to the test DSN and ParseDSNToConfig\", fieldName)\n\t\t}\n\t}\n\n\tassert.Equal(t, \"https://user:pass@localhost:8080\", config.ServerURI)\n\tassert.Equal(t, \"test-source\", config.Source)\n\tassert.Equal(t, \"test_catalog\", config.Catalog)\n\tassert.Equal(t, \"test_schema\", config.Schema)\n\tassert.Equal(t, map[string]string{\"prop1\": \"value1\", \"prop2\": \"value2\"}, config.SessionProperties)\n\tassert.Equal(t, map[string]string{\"cred1\": \"secret1\", \"cred2\": \"secret2\"}, config.ExtraCredentials)\n\tassert.Equal(t, []string{\"tag1\", \"tag2\", \"tag3\"}, config.ClientTags)\n\tassert.Equal(t, \"test_client\", config.CustomClientName)\n\tassert.Equal(t, true, config.KerberosEnabled)\n\tassert.Equal(t, \"/path/to/keytab\", config.KerberosKeytabPath)\n\tassert.Equal(t, \"user@REALM.COM\", config.KerberosPrincipal)\n\tassert.Equal(t, \"trino-service\", config.KerberosRemoteServiceName)\n\tassert.Equal(t, \"REALM.COM\", config.KerberosRealm)\n\tassert.Equal(t, \"/etc/krb5.conf\", config.KerberosConfigPath)\n\tassert.Equal(t, \"/path/to/cert.pem\", config.SSLCertPath)\n\tassert.Equal(t, \"-----BEGIN CERTIFICATE-----test-cert-----END CERTIFICATE-----\", config.SSLCert)\n\tassert.Equal(t, \"jwt-token-here\", config.AccessToken)\n\tassert.Equal(t, true, config.DisableExplicitPrepare)\n\tassert.Equal(t, true, config.ForwardAuthorizationHeader)\n\tassert.NotNil(t, config.QueryTimeout)\n\tassert.Equal(t, 5*time.Minute+30*time.Second, *config.QueryTimeout)\n\tassert.Equal(t, map[string]string{\"catalog1\": \"role1\", \"catalog2\": \"role2\"}, config.Roles)\n}\n\nfunc TestConfigFormatDSNTags(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\tconfig *Config\n\t\twant   string\n\t}{\n\t\t{\n\t\t\tname: \"multiple tags\",\n\t\t\tconfig: &Config{\n\t\t\t\tServerURI:         \"http://foobar@localhost:8080\",\n\t\t\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t\t\t\tClientTags:        []string{\"test1\", \"test2\", \"test3\"},\n\t\t\t},\n\t\t\twant: \"http://foobar@localhost:8080?clientTags=test1%2Ctest2%2Ctest3&session_properties=query_priority%3A1&source=trino-go-client\",\n\t\t},\n\t\t{\n\t\t\tname: \"single tag\",\n\t\t\tconfig: &Config{\n\t\t\t\tServerURI:         \"http://foobar@localhost:8080\",\n\t\t\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t\t\t\tClientTags:        []string{\"test1\"},\n\t\t\t},\n\t\t\twant: \"http://foobar@localhost:8080?clientTags=test1&session_properties=query_priority%3A1&source=trino-go-client\",\n\t\t},\n\t\t{\n\t\t\tname: \"multiple tags with special characters\",\n\t\t\tconfig: &Config{\n\t\t\t\tServerURI:         \"http://foobar@localhost:8080\",\n\t\t\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t\t\t\tClientTags:        []string{\"foo %20\", \"bar=test\", \"baz#tag\"},\n\t\t\t},\n\t\t\twant: \"http://foobar@localhost:8080?clientTags=foo+%2520%2Cbar%3Dtest%2Cbaz%23tag&session_properties=query_priority%3A1&source=trino-go-client\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.config.FormatDSN()\n\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, tt.want, got)\n\n\t\t})\n\t}\n}\n\nfunc TestConfigSSLCertPath(t *testing.T) {\n\tc := &Config{\n\t\tServerURI:         \"https://foobar@localhost:8080\",\n\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t\tSSLCertPath:       \"cert.pem\",\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\twant := \"https://foobar@localhost:8080?SSLCertPath=cert.pem&session_properties=query_priority%3A1&source=trino-go-client\"\n\n\tassert.Equal(t, want, dsn)\n}\n\nfunc TestConfigSSLCert(t *testing.T) {\n\tsslCert := `-----BEGIN CERTIFICATE-----\nMIIFijCCA3ICCQDngXKCZFwSazANBgkqhkiG9w0BAQsFADCBhjELMAkGA1UEBhMC\nWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwIQ2l0eU5hbWUxFDASBgNV\nBAoMC0NvbXBhbnlOYW1lMRswGQYDVQQLDBJDb21wYW55U2VjdGlvbk5hbWUxHTAb\nBgNVBAMMFENvbW1vbk5hbWVPckhvc3RuYW1lMB4XDTIzMDUxNzE2MzQ0MloXDTMz\nMDUxNDE2MzQ0MlowgYYxCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0ZU5hbWUx\nETAPBgNVBAcMCENpdHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEbMBkGA1UE\nCwwSQ29tcGFueVNlY3Rpb25OYW1lMR0wGwYDVQQDDBRDb21tb25OYW1lT3JIb3N0\nbmFtZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKzz/SIuOiHZbUAH\nxCWrMaiJybdHHHl0smCu50XKvl/ZkszO1c4aES8/Vohw44ttaE+GOknTSGPka356\nNqwdPYMjnXN0d5HY5T5nOfgLxGD/1iCHACrT4gkd1asJ7eFaUgud0a+e9+oG53Vh\nZ3QV8+5JaWPuBMudJ8EOtrPMd0dJKVzeExTbpQLJ9HdIsHc6DXqshACd8Iy+ezqf\nOoYMYyJMAHO86MZrTs3t9AwUADlvntrwwObVrZ3v43IOKwJTRnpImmVlkouKrGn/\nHKzRmJEJ6hJQXhuhqI/0rr61XR8aa8Gs0FqtTTMJ32+PciPPzFtFVLAeA417lYz+\nuXZ6IpTLK4oDH8Q6gJY80GYqcGc+01ZY90W2L+odTz9P74vnTvsUgSjOcy7prJ0+\nWxoeBNPvkLeetX9WDZW4XaR++HVO1qelNJQqeB6Nver9MJdKkXvR3OxT6iluqXfA\nl9JJ57tnzspSrttjWG4kwwiaGn/4xPqd95Hp0r1WAK8U0Cqtvz+Zw9jl341tC1Ya\nK1KFIErZYf0KX8ZiYvmkHaTRxYiCmFnnfLtGdrAWkacisLKMhjeb9LXwC/TVtvio\na+ofiW2DX80pQptkfNJs9P19ZFEojPAEFHiZFpz5yZSxHglxIsdIhRsuy5xb/KTo\nzey3tsKQJaFIah+aHKjyn3uZx2IRAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAIs5\nsbCMB6bT0hcNFqFRCI/BL23m5jwdL9kNWDlEQxBvErtzTC+uStGrCqwV+qu49QAZ\n64kUolbzFyq/hQFpHd+9EzNkZGbiOf5toWaBUP6jaZzqYPdfDW+AwIA7iPHcqwH1\niWX2zuAWAICy4H+S4oa/ShOPc8BrrnS8k5f1NpergOhd+wl+szuXJN9Tjli3wd/k\nL7f86xvZfOrEbss8YP4QE0+mKh6G71NLEVQ4SV7yIE2hCNLDFWS2ltGVRLv6CDaQ\nfXIQrZx2Khvpj+HI/hrwm1wV8Cg5w2IvB831YjTSepSoos0Cc/qYC78zqol/NbwL\n7TdHtuZKukDrisRiCDdoKFmS1/IUVeVR2352CG8G3Zo0wwfzoKLxLUtunnrKMmmO\nr2jXykqP2hb1dApBNFM7FoaJ7a0j6EcURW8wYl4I+b9ymftPnnZ8mgrjwvLh5ETj\nRgGsIBychLZoc1WWTZWu62+mvmSJnzEIFfaiSeYZLaL6qFHm6kqsAUn4s1Looj8/\nXoCNjMecchWbpHGCPwMFH1k2smxu7bKk/RJNuWSVn1IPUceJnOBHZGj92aJGZpjr\n8j39T3dK9F2r5rHwjZpeEIhyhbLw6pYKif+lBgAWJD3waG0ycwURA02/POHN4CpT\nFKu5ZAlRfb2aYegr49DHhzoVAdInWQmP+5EZEUD1\n-----END CERTIFICATE-----`\n\tc := &Config{\n\t\tServerURI:         \"https://foobar@localhost:8080\",\n\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t\tSSLCert:           sslCert,\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\twant := \"https://foobar@localhost:8080?SSLCert=\" + url.QueryEscape(sslCert) + \"&session_properties=query_priority%3A1&source=trino-go-client\"\n\n\tassert.Equal(t, want, dsn)\n}\n\nfunc TestExtraCredentials(t *testing.T) {\n\tc := &Config{\n\t\tServerURI:        \"http://foobar@localhost:8080\",\n\t\tExtraCredentials: map[string]string{\"token\": \"mYtOkEn\", \"otherToken\": \"oThErToKeN%*!#@special\"},\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\twant := \"http://foobar@localhost:8080?extra_credentials=otherToken%3AoThErToKeN%25%2A%21%23%40special%3Btoken%3AmYtOkEn&source=trino-go-client\"\n\tassert.Equal(t, want, dsn)\n}\n\nfunc TestInvalidExtraCredentials(t *testing.T) {\n\ttestcases := []struct {\n\t\tName        string\n\t\tCredentials map[string]string\n\t\tError       string\n\t}{\n\t\t{\n\t\t\tName:        \"Empty key\",\n\t\t\tCredentials: map[string]string{\"\": \"emptyKey\"},\n\t\t\tError:       \"trino: extra_credentials key is empty\",\n\t\t},\n\t\t{\n\t\t\tName:        \"Empty value\",\n\t\t\tCredentials: map[string]string{\"valid\": \"a\", \"emptyValue\": \"\"},\n\t\t\tError:       \"trino: extra_credentials value is empty\",\n\t\t},\n\t\t{\n\t\t\tName:        \"Unprintable key\",\n\t\t\tCredentials: map[string]string{\"😊\": \"unprintableKey\"},\n\t\t\tError:       \"trino: extra_credentials key '😊' contains spaces or is not printable ASCII\",\n\t\t},\n\t\t{\n\t\t\tName:        \"Unprintable value\",\n\t\t\tCredentials: map[string]string{\"unprintableValue\": \"😊\"},\n\t\t\tError:       \"trino: extra_credentials value for key 'unprintableValue' contains spaces or is not printable ASCII\",\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tc := &Config{\n\t\t\t\tServerURI:        \"http://foobar@localhost:8080\",\n\t\t\t\tExtraCredentials: tc.Credentials,\n\t\t\t}\n\t\t\tdsn, err := c.FormatDSN()\n\t\t\trequire.NoError(t, err)\n\t\t\tdb, err := sql.Open(\"trino\", dsn)\n\t\t\trequire.NoError(t, err)\n\t\t\terr = db.Ping()\n\t\t\tassert.EqualError(t, err, tc.Error)\n\t\t})\n\t}\n}\n\nfunc TestConfigWithoutSSLCertPath(t *testing.T) {\n\tc := &Config{\n\t\tServerURI:         \"https://foobar@localhost:8080\",\n\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t}\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\twant := \"https://foobar@localhost:8080?session_properties=query_priority%3A1&source=trino-go-client\"\n\n\tassert.Equal(t, want, dsn)\n}\n\nfunc TestKerberosConfig(t *testing.T) {\n\tc := &Config{\n\t\tServerURI:                 \"https://foobar@localhost:8090\",\n\t\tSessionProperties:         map[string]string{\"query_priority\": \"1\"},\n\t\tKerberosEnabled:           true,\n\t\tKerberosKeytabPath:        \"/opt/test.keytab\",\n\t\tKerberosPrincipal:         \"trino/testhost\",\n\t\tKerberosRealm:             \"example.com\",\n\t\tKerberosConfigPath:        \"/etc/krb5.conf\",\n\t\tKerberosRemoteServiceName: \"service\",\n\t\tSSLCertPath:               \"/tmp/test.cert\",\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\twant := \"https://foobar@localhost:8090?KerberosConfigPath=%2Fetc%2Fkrb5.conf&KerberosEnabled=true&KerberosKeytabPath=%2Fopt%2Ftest.keytab&KerberosPrincipal=trino%2Ftesthost&KerberosRealm=example.com&KerberosRemoteServiceName=service&SSLCertPath=%2Ftmp%2Ftest.cert&session_properties=query_priority%3A1&source=trino-go-client\"\n\n\tassert.Equal(t, want, dsn)\n}\n\nfunc TestFormatDSNWithRoles(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tconfig      *Config\n\t\twantDSN     string\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Multiple catalog roles\",\n\t\t\tconfig: &Config{\n\t\t\t\tServerURI:         \"https://foobar@localhost:8090\",\n\t\t\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t\t\t\tRoles:             map[string]string{\"catalog1\": \"role1\", \"catalog2\": \"role2\"},\n\t\t\t},\n\t\t\twantDSN: \"https://foobar@localhost:8090?roles=catalog1%3Arole1%3Bcatalog2%3Arole2&session_properties=query_priority%3A1&source=trino-go-client\",\n\t\t},\n\t\t{\n\t\t\tname: \"Single catalog role\",\n\t\t\tconfig: &Config{\n\t\t\t\tServerURI:         \"https://foobar@localhost:8090\",\n\t\t\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t\t\t\tRoles:             map[string]string{\"catalog1\": \"role1\"},\n\t\t\t},\n\t\t\twantDSN: \"https://foobar@localhost:8090?roles=catalog1%3Arole1&session_properties=query_priority%3A1&source=trino-go-client\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdsn, err := tt.config.FormatDSN()\n\t\t\tif tt.expectError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, tt.wantDSN, dsn)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestInvalidKerberosConfig(t *testing.T) {\n\tc := &Config{\n\t\tServerURI:       \"http://foobar@localhost:8090\",\n\t\tKerberosEnabled: true,\n\t}\n\n\t_, err := c.FormatDSN()\n\tassert.Error(t, err, \"dsn generated from invalid secure url, since kerberos enabled must has SSL enabled\")\n}\n\nfunc TestAccessTokenConfig(t *testing.T) {\n\tc := &Config{\n\t\tServerURI:   \"https://foobar@localhost:8090\",\n\t\tAccessToken: \"token\",\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\twant := \"https://foobar@localhost:8090?accessToken=token&source=trino-go-client\"\n\n\tassert.Equal(t, want, dsn)\n}\n\nfunc TestConfigWithMalformedURL(t *testing.T) {\n\t_, err := (&Config{ServerURI: \":(\"}).FormatDSN()\n\tassert.Error(t, err, \"dsn generated from malformed url\")\n}\n\nfunc TestConnErrorDSN(t *testing.T) {\n\ttestcases := []struct {\n\t\tName string\n\t\tDSN  string\n\t}{\n\t\t{Name: \"malformed\", DSN: \"://\"},\n\t\t{Name: \"unknown_client\", DSN: \"http://localhost?custom_client=unknown\"},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tdb, err := sql.Open(\"trino\", tc.DSN)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, err = db.Query(\"SELECT 1\")\n\t\t\tassert.Errorf(t, err, \"test dsn is supposed to fail: %s\", tc.DSN)\n\n\t\t\tif err == nil {\n\t\t\t\trequire.NoError(t, db.Close())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRegisterCustomClientReserved(t *testing.T) {\n\tfor _, tc := range []string{\"true\", \"false\"} {\n\t\tt.Run(fmt.Sprintf(\"%v\", tc), func(t *testing.T) {\n\t\t\trequire.Errorf(t,\n\t\t\t\tRegisterCustomClient(tc, &http.Client{}),\n\t\t\t\t\"client key name supposed to fail: %s\", tc)\n\t\t})\n\t}\n}\n\nfunc TestQueryTimeout(t *testing.T) {\n\ttimeout := 10 * time.Second\n\tc := &Config{\n\t\tServerURI:    \"https://foobar@localhost:8090\",\n\t\tQueryTimeout: &timeout,\n\t}\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\twant := \"https://foobar@localhost:8090?query_timeout=10s&source=trino-go-client\"\n\tassert.Equal(t, want, dsn)\n}\n\nfunc TestRoundTripRetryQueryError(t *testing.T) {\n\ttestcases := []struct {\n\t\tName                string\n\t\tHttpStatus          int\n\t\tExpectedErrorStatus string\n\t}{\n\t\t{\n\t\t\tName:                \"Test retry 502 Bad Gateway\",\n\t\t\tHttpStatus:          http.StatusBadGateway,\n\t\t\tExpectedErrorStatus: \"200 OK\",\n\t\t},\n\t\t{\n\t\t\tName:                \"Test retry 503 Service Unavailable\",\n\t\t\tHttpStatus:          http.StatusServiceUnavailable,\n\t\t\tExpectedErrorStatus: \"200 OK\",\n\t\t},\n\t\t{\n\t\t\tName:                \"Test retry 504 Gateway Timeout\",\n\t\t\tHttpStatus:          http.StatusGatewayTimeout,\n\t\t\tExpectedErrorStatus: \"200 OK\",\n\t\t},\n\t\t{\n\t\t\tName:                \"Test no retry 404 Not Found\",\n\t\t\tHttpStatus:          http.StatusNotFound,\n\t\t\tExpectedErrorStatus: \"404 Not Found\",\n\t\t},\n\t}\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tcount := 0\n\t\t\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif count == 0 {\n\t\t\t\t\tcount++\n\t\t\t\t\tw.WriteHeader(tc.HttpStatus)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t\tjson.NewEncoder(w).Encode(&stmtResponse{\n\t\t\t\t\tError: ErrTrino{\n\t\t\t\t\t\tErrorName: \"TEST\",\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}))\n\n\t\t\tt.Cleanup(ts.Close)\n\n\t\t\tdb, err := sql.Open(\"trino\", ts.URL)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tt.Cleanup(func() {\n\t\t\t\tassert.NoError(t, db.Close())\n\t\t\t})\n\n\t\t\t_, err = db.Query(\"SELECT 1\")\n\t\t\tassert.ErrorContains(t, err, tc.ExpectedErrorStatus, \"unexpected error: %w\", err)\n\t\t})\n\t}\n}\n\nfunc TestRoundTripBogusData(t *testing.T) {\n\tcount := 0\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif count == 0 {\n\t\t\tcount++\n\t\t\tw.WriteHeader(http.StatusServiceUnavailable)\n\t\t\treturn\n\t\t}\n\t\tw.WriteHeader(http.StatusOK)\n\t\t// some invalid JSON\n\t\tw.Write([]byte(`{\"stats\": {\"progressPercentage\": \"\"}}`))\n\t}))\n\n\tt.Cleanup(ts.Close)\n\n\tdb, err := sql.Open(\"trino\", ts.URL)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\trows, err := db.Query(\"SELECT 1\")\n\trequire.NoError(t, err)\n\tassert.False(t, rows.Next())\n\trequire.NoError(t, rows.Err())\n}\n\nfunc TestRoundTripCancellation(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusServiceUnavailable)\n\t}))\n\n\tt.Cleanup(ts.Close)\n\n\tdb, err := sql.Open(\"trino\", ts.URL)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)\n\tt.Cleanup(cancel)\n\n\t_, err = db.QueryContext(ctx, \"SELECT 1\")\n\tassert.Error(t, err, \"unexpected query with cancelled context succeeded\")\n}\n\nfunc TestAuthFailure(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusUnauthorized)\n\t}))\n\n\tt.Cleanup(ts.Close)\n\n\tdb, err := sql.Open(\"trino\", ts.URL)\n\trequire.NoError(t, err)\n\n\tassert.NoError(t, db.Close())\n}\n\nfunc TestTokenAuth(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Header.Get(\"Authorization\") != \"Bearer token\" {\n\t\t\tw.WriteHeader(http.StatusUnauthorized)\n\t\t} else {\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t}\n\t}))\n\n\tt.Cleanup(ts.Close)\n\n\tdb, err := sql.Open(\"trino\", ts.URL+\"?accessToken=token\")\n\trequire.NoError(t, err)\n\n\t_, err = db.Query(\"SELECT 1\")\n\trequire.Error(t, err, \"trino: EOF\")\n\n\tassert.NoError(t, db.Close())\n}\n\nfunc TestQueryForUsername(t *testing.T) {\n\tif testing.Short() {\n\t\tt.Skip(\"Skipping test in short mode.\")\n\t}\n\tc := &Config{\n\t\tServerURI:         *integrationServerFlag,\n\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\tdb, err := sql.Open(\"trino\", dsn)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\trows, err := db.Query(\"SELECT current_user\", sql.Named(\"X-Trino-User\", string(\"TestUser\")))\n\trequire.NoError(t, err, \"Failed executing query\")\n\tassert.NotNil(t, rows)\n\n\tfor rows.Next() {\n\t\tvar user string\n\t\trequire.NoError(t, rows.Scan(&user), \"Failed scanning query result\")\n\n\t\tassert.Equal(t, \"TestUser\", user, \"Expected value does not equal result value\")\n\t}\n}\n\ntype TestQueryProgressCallback struct {\n\tprogressMap map[time.Time]float64\n\tstatusMap   map[time.Time]string\n}\n\nfunc (qpc *TestQueryProgressCallback) Update(qpi QueryProgressInfo) {\n\tqpc.progressMap[time.Now()] = float64(qpi.QueryStats.ProgressPercentage)\n\tqpc.statusMap[time.Now()] = qpi.QueryStats.State\n}\n\nfunc TestQueryProgressWithCallback(t *testing.T) {\n\tif testing.Short() {\n\t\tt.Skip(\"Skipping test in short mode.\")\n\t}\n\tc := &Config{\n\t\tServerURI:         *integrationServerFlag,\n\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\tdb, err := sql.Open(\"trino\", dsn)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\tcallback := &TestQueryProgressCallback{}\n\n\t_, err = db.Query(\"SELECT 2\", sql.Named(\"X-Trino-Progress-Callback\", callback))\n\tassert.EqualError(t, err, ErrInvalidProgressCallbackHeader.Error(), \"unexpected error\")\n}\n\nfunc TestQueryProgressWithCallbackPeriod(t *testing.T) {\n\tif testing.Short() {\n\t\tt.Skip(\"Skipping test in short mode.\")\n\t}\n\tc := &Config{\n\t\tServerURI:         *integrationServerFlag,\n\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\tdb, err := sql.Open(\"trino\", dsn)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\tprogressMap := make(map[time.Time]float64)\n\tstatusMap := make(map[time.Time]string)\n\tprogressUpdater := &TestQueryProgressCallback{\n\t\tprogressMap: progressMap,\n\t\tstatusMap:   statusMap,\n\t}\n\tprogressUpdaterPeriod, err := time.ParseDuration(\"1ms\")\n\trequire.NoError(t, err)\n\n\trows, err := db.Query(\"SELECT 2\",\n\t\tsql.Named(\"X-Trino-Progress-Callback\", progressUpdater),\n\t\tsql.Named(\"X-Trino-Progress-Callback-Period\", progressUpdaterPeriod),\n\t)\n\trequire.NoError(t, err, \"Failed executing query\")\n\tassert.NotNil(t, rows)\n\n\tfor rows.Next() {\n\t\tvar ts string\n\t\trequire.NoError(t, rows.Scan(&ts), \"Failed scanning query result\")\n\n\t\tassert.Equal(t, \"2\", ts, \"Expected value does not equal result value\")\n\t}\n\n\tif err = rows.Err(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err = rows.Close(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// sort time in order to calculate interval\n\tassert.NotEmpty(t, progressMap)\n\tassert.NotEmpty(t, statusMap)\n\tvar keys []time.Time\n\tfor k := range statusMap {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Slice(keys, func(i, j int) bool {\n\t\treturn keys[i].Before(keys[j])\n\t})\n\n\tfor i, k := range keys {\n\t\tif i > 0 {\n\t\t\tassert.GreaterOrEqual(t, k.Sub(keys[i-1]), progressUpdaterPeriod)\n\t\t}\n\t\tassert.GreaterOrEqual(t, progressMap[k], 0.0)\n\t}\n}\n\nfunc TestQueryColumns(t *testing.T) {\n\tc := &Config{\n\t\tServerURI:         *integrationServerFlag,\n\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\tdb, err := sql.Open(\"trino\", dsn)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\trows, err := db.Query(`SELECT\n  true AS bool,\n  cast(123 AS tinyint) AS tinyint,\n  cast(456 AS smallint) AS smallint,\n  cast(678 AS integer) AS integer,\n  cast(1234 AS bigint) AS bigint,\n  cast(1.23 AS real) AS real,\n  cast(1.23 AS double) AS double,\n  cast(1.23 as decimal(10,5)) AS decimal,\n  cast('aaa' as varchar) AS vunbounded,\n  cast('bbb' as varchar(10)) AS vbounded,\n  cast('ccc' AS char) AS cunbounded,\n  cast('ddd' as char(10)) AS cbounded,\n  cast('ddd' as varbinary) AS varbinary,\n  cast('{\"aaa\": 1}' as json) AS json,\n  current_date AS date,\n  cast(current_time as time) AS time,\n  cast(current_time as time(6)) AS timep,\n  cast(current_time as time with time zone) AS timetz,\n  cast(current_time as timestamp) AS ts,\n  cast(current_time as timestamp(6)) AS tsp,\n  cast(current_time as timestamp with time zone) AS tstz,\n  cast(current_time as timestamp(6) with time zone) AS tsptz,\n  interval '3' month AS ytm,\n  interval '2' day AS dts,\n  array['a', 'b'] AS varray,\n  array[array['a'], array['b']] AS v2array,\n  array[array[array['a'], array['b']]] AS v3array,\n  map(array['a'], array[1]) AS map,\n  array[map(array['a'], array[1]), map(array['b'], array[2])] AS marray,\n  row('a', 1) AS row,\n  cast(row('a', 1.23) AS row(x varchar, y double)) AS named_row,\n  ipaddress '10.0.0.1' AS ip,\n  uuid '12151fd2-7586-11e9-8f9e-2a86e4085a59' AS uuid`)\n\trequire.NoError(t, err, \"Failed executing query\")\n\tassert.NotNil(t, rows)\n\n\tcolumns, err := rows.Columns()\n\trequire.NoError(t, err, \"Failed reading result columns\")\n\n\tassert.Equal(t, 33, len(columns), \"Expected 33 result column\")\n\texpectedNames := []string{\n\t\t\"bool\",\n\t\t\"tinyint\",\n\t\t\"smallint\",\n\t\t\"integer\",\n\t\t\"bigint\",\n\t\t\"real\",\n\t\t\"double\",\n\t\t\"decimal\",\n\t\t\"vunbounded\",\n\t\t\"vbounded\",\n\t\t\"cunbounded\",\n\t\t\"cbounded\",\n\t\t\"varbinary\",\n\t\t\"json\",\n\t\t\"date\",\n\t\t\"time\",\n\t\t\"timep\",\n\t\t\"timetz\",\n\t\t\"ts\",\n\t\t\"tsp\",\n\t\t\"tstz\",\n\t\t\"tsptz\",\n\t\t\"ytm\",\n\t\t\"dts\",\n\t\t\"varray\",\n\t\t\"v2array\",\n\t\t\"v3array\",\n\t\t\"map\",\n\t\t\"marray\",\n\t\t\"row\",\n\t\t\"named_row\",\n\t\t\"ip\",\n\t\t\"uuid\",\n\t}\n\tassert.Equal(t, expectedNames, columns)\n\n\tcolumnTypes, err := rows.ColumnTypes()\n\trequire.NoError(t, err, \"Failed reading result column types\")\n\n\tassert.Equal(t, 33, len(columnTypes), \"Expected 33 result column type\")\n\n\ttype columnType struct {\n\t\ttypeName  string\n\t\thasScale  bool\n\t\tprecision int64\n\t\tscale     int64\n\t\thasLength bool\n\t\tlength    int64\n\t\tscanType  reflect.Type\n\t}\n\texpectedTypes := []columnType{\n\t\t{\n\t\t\t\"BOOLEAN\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullBool{}),\n\t\t},\n\t\t{\n\t\t\t\"TINYINT\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullInt32{}),\n\t\t},\n\t\t{\n\t\t\t\"SMALLINT\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullInt32{}),\n\t\t},\n\t\t{\n\t\t\t\"INTEGER\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullInt32{}),\n\t\t},\n\t\t{\n\t\t\t\"BIGINT\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullInt64{}),\n\t\t},\n\t\t{\n\t\t\t\"REAL\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullFloat64{}),\n\t\t},\n\t\t{\n\t\t\t\"DOUBLE\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullFloat64{}),\n\t\t},\n\t\t{\n\t\t\t\"DECIMAL\",\n\t\t\ttrue,\n\t\t\t10,\n\t\t\t5,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullString{}),\n\t\t},\n\t\t{\n\t\t\t\"VARCHAR\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\ttrue,\n\t\t\tmath.MaxInt32,\n\t\t\treflect.TypeOf(sql.NullString{}),\n\t\t},\n\t\t{\n\t\t\t\"VARCHAR\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\ttrue,\n\t\t\t10,\n\t\t\treflect.TypeOf(sql.NullString{}),\n\t\t},\n\t\t{\n\t\t\t\"CHAR\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\ttrue,\n\t\t\t1,\n\t\t\treflect.TypeOf(sql.NullString{}),\n\t\t},\n\t\t{\n\t\t\t\"CHAR\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\ttrue,\n\t\t\t10,\n\t\t\treflect.TypeOf(sql.NullString{}),\n\t\t},\n\t\t{\n\t\t\t\"VARBINARY\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf([]byte{}),\n\t\t},\n\t\t{\n\t\t\t\"JSON\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullString{}),\n\t\t},\n\t\t{\n\t\t\t\"DATE\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullTime{}),\n\t\t},\n\t\t{\n\t\t\t\"TIME\",\n\t\t\ttrue,\n\t\t\t3,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullTime{}),\n\t\t},\n\t\t{\n\t\t\t\"TIME\",\n\t\t\ttrue,\n\t\t\t6,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullTime{}),\n\t\t},\n\t\t{\n\t\t\t\"TIME WITH TIME ZONE\",\n\t\t\ttrue,\n\t\t\t3,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullTime{}),\n\t\t},\n\t\t{\n\t\t\t\"TIMESTAMP\",\n\t\t\ttrue,\n\t\t\t3,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullTime{}),\n\t\t},\n\t\t{\n\t\t\t\"TIMESTAMP\",\n\t\t\ttrue,\n\t\t\t6,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullTime{}),\n\t\t},\n\t\t{\n\t\t\t\"TIMESTAMP WITH TIME ZONE\",\n\t\t\ttrue,\n\t\t\t3,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullTime{}),\n\t\t},\n\t\t{\n\t\t\t\"TIMESTAMP WITH TIME ZONE\",\n\t\t\ttrue,\n\t\t\t6,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullTime{}),\n\t\t},\n\t\t{\n\t\t\t\"INTERVAL YEAR TO MONTH\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullString{}),\n\t\t},\n\t\t{\n\t\t\t\"INTERVAL DAY TO SECOND\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullString{}),\n\t\t},\n\t\t{\n\t\t\t\"ARRAY(VARCHAR(1))\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(NullSliceString{}),\n\t\t},\n\t\t{\n\t\t\t\"ARRAY(ARRAY(VARCHAR(1)))\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(NullSlice2String{}),\n\t\t},\n\t\t{\n\t\t\t\"ARRAY(ARRAY(ARRAY(VARCHAR(1))))\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(NullSlice3String{}),\n\t\t},\n\t\t{\n\t\t\t\"MAP(VARCHAR(1), INTEGER)\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(NullMap{}),\n\t\t},\n\t\t{\n\t\t\t\"ARRAY(MAP(VARCHAR(1), INTEGER))\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(NullSliceMap{}),\n\t\t},\n\t\t{\n\t\t\t\"ROW(VARCHAR(1), INTEGER)\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(new(interface{})).Elem(),\n\t\t},\n\t\t{\n\t\t\t\"ROW(X VARCHAR, Y DOUBLE)\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(new(interface{})).Elem(),\n\t\t},\n\t\t{\n\t\t\t\"IPADDRESS\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullString{}),\n\t\t},\n\t\t{\n\t\t\t\"UUID\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullString{}),\n\t\t},\n\t}\n\tactualTypes := make([]columnType, 33)\n\tfor i, column := range columnTypes {\n\t\tactualTypes[i].typeName = column.DatabaseTypeName()\n\t\tactualTypes[i].precision, actualTypes[i].scale, actualTypes[i].hasScale = column.DecimalSize()\n\t\tactualTypes[i].length, actualTypes[i].hasLength = column.Length()\n\t\tactualTypes[i].scanType = column.ScanType()\n\t}\n\n\tassert.Equal(t, actualTypes, expectedTypes)\n}\n\nfunc TestMaxGoPrecisionDateTime(t *testing.T) {\n\tc := &Config{\n\t\tServerURI:         *integrationServerFlag,\n\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\tdb, err := sql.Open(\"trino\", dsn)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\trows, err := db.Query(`SELECT\n  cast(current_time as time(9)) AS timep,\n  cast(current_time as time(9) with time zone) AS timeptz,\n  cast(current_time as timestamp(9)) AS tsp,\n  cast(current_time as timestamp(9) with time zone) AS tsptz`)\n\trequire.NoError(t, err, \"Failed executing query\")\n\tassert.NotNil(t, rows)\n\n\tcolumns, err := rows.Columns()\n\trequire.NoError(t, err, \"Failed reading result columns\")\n\n\tassert.Equal(t, 4, len(columns), \"Expected 4 result column\")\n\texpectedNames := []string{\n\t\t\"timep\",\n\t\t\"timeptz\",\n\t\t\"tsp\",\n\t\t\"tsptz\",\n\t}\n\tassert.Equal(t, expectedNames, columns)\n\n\tcolumnTypes, err := rows.ColumnTypes()\n\trequire.NoError(t, err, \"Failed reading result column types\")\n\n\tassert.Equal(t, 4, len(columnTypes), \"Expected 4 result column type\")\n\n\ttype columnType struct {\n\t\ttypeName  string\n\t\thasScale  bool\n\t\tprecision int64\n\t\tscale     int64\n\t\thasLength bool\n\t\tlength    int64\n\t\tscanType  reflect.Type\n\t}\n\texpectedTypes := []columnType{\n\t\t{\n\t\t\t\"TIME\",\n\t\t\ttrue,\n\t\t\t9,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullTime{}),\n\t\t},\n\t\t{\n\t\t\t\"TIME WITH TIME ZONE\",\n\t\t\ttrue,\n\t\t\t9,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullTime{}),\n\t\t},\n\t\t{\n\t\t\t\"TIMESTAMP\",\n\t\t\ttrue,\n\t\t\t9,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullTime{}),\n\t\t},\n\t\t{\n\t\t\t\"TIMESTAMP WITH TIME ZONE\",\n\t\t\ttrue,\n\t\t\t9,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\treflect.TypeOf(sql.NullTime{}),\n\t\t},\n\t}\n\tactualTypes := make([]columnType, 4)\n\tfor i, column := range columnTypes {\n\t\tactualTypes[i].typeName = column.DatabaseTypeName()\n\t\tactualTypes[i].precision, actualTypes[i].scale, actualTypes[i].hasScale = column.DecimalSize()\n\t\tactualTypes[i].length, actualTypes[i].hasLength = column.Length()\n\t\tactualTypes[i].scanType = column.ScanType()\n\t}\n\n\tassert.Equal(t, actualTypes, expectedTypes)\n\n\tassert.True(t, rows.Next())\n\trequire.NoError(t, rows.Err())\n\n}\n\nfunc TestQueryCancellation(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\tjson.NewEncoder(w).Encode(&stmtResponse{\n\t\t\tError: ErrTrino{\n\t\t\t\tErrorName: \"USER_CANCELLED\",\n\t\t\t},\n\t\t})\n\t}))\n\n\tt.Cleanup(ts.Close)\n\n\tdb, err := sql.Open(\"trino\", ts.URL)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\t_, err = db.Query(\"SELECT 1\")\n\tassert.EqualError(t, err, ErrQueryCancelled.Error(), \"unexpected error\")\n}\n\nfunc TestRoleHeader(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\troles          map[string]string\n\t\tnamedArgRoles  map[string]string\n\t\texpectedHeader string\n\t}{\n\t\t{\n\t\t\tname:           \"Roles from config\",\n\t\t\troles:          map[string]string{\"catalog1\": \"role1\", \"catalog2\": \"role2\"},\n\t\t\tnamedArgRoles:  nil,\n\t\t\texpectedHeader: `catalog1=ROLE{role1},catalog2=ROLE{role2}`,\n\t\t},\n\t\t{\n\t\t\tname:           \"Override dsn roles with named argument\",\n\t\t\troles:          map[string]string{\"catalog1\": \"role1\"},\n\t\t\tnamedArgRoles:  map[string]string{\"catalog3\": \"role3\", \"catalog4\": \"role4\", \"catalog5\": \"ALL\"},\n\t\t\texpectedHeader: `catalog3=ROLE{role3},catalog4=ROLE{role4},catalog5=ALL`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar receivedHeader string\n\t\t\tvar serverURL string\n\t\t\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\treceivedHeader = r.Header.Get(trinoRoleHeader)\n\t\t\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t\t_, _ = w.Write([]byte(`{\"id\":\"1\",\"nextUri\":\"` + serverURL + `/1\"}`))\n\t\t\t}))\n\t\t\tserverURL = ts.URL\n\t\t\tt.Cleanup(ts.Close)\n\n\t\t\tc := &Config{\n\t\t\t\tServerURI: ts.URL,\n\t\t\t\tRoles:     tt.roles,\n\t\t\t}\n\n\t\t\tdsn, err := c.FormatDSN()\n\t\t\trequire.NoError(t, err)\n\t\t\tdb, err := sql.Open(\"trino\", dsn)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif tt.namedArgRoles != nil {\n\t\t\t\t_, _ = db.Query(\"SELECT 1\", sql.Named(\"X-Trino-Role\", tt.namedArgRoles))\n\t\t\t} else {\n\t\t\t\t_, _ = db.Query(\"SELECT 1\")\n\t\t\t}\n\n\t\t\tassert.Equal(t, tt.expectedHeader, receivedHeader, \"expected X-Trino-Role header to match\")\n\t\t})\n\t}\n}\n\nfunc TestQueryFailure(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t}))\n\n\tt.Cleanup(ts.Close)\n\n\tdb, err := sql.Open(\"trino\", ts.URL)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\t_, err = db.Query(\"SELECT 1\")\n\tassert.IsTypef(t, new(ErrQueryFailed), err, \"unexpected error: %w\", err)\n}\n\n// This test ensures that the fetch method is not generating stack overflow errors.\n// === RUN   TestFetchNoStackOverflow\n// runtime: goroutine stack exceeds 1000000000-byte limit\n// runtime: sp=0x14037b00390 stack=[0x14037b00000, 0x14057b00000]\n// fatal error: stack overflow\nfunc TestFetchNoStackOverflow(t *testing.T) {\n\tpreviousSetting := debug.SetMaxStack(50 * 1024)\n\tdefer debug.SetMaxStack(previousSetting)\n\tcount := 0\n\tvar buf *bytes.Buffer\n\tvar ts *httptest.Server\n\tts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif count <= 50 {\n\t\t\tif buf == nil {\n\t\t\t\tbuf = new(bytes.Buffer)\n\t\t\t\tjson.NewEncoder(buf).Encode(&stmtResponse{\n\t\t\t\t\tID:      \"fake-query\",\n\t\t\t\t\tNextURI: ts.URL + \"/v1/statement/20210817_140827_00000_arvdv/1\",\n\t\t\t\t})\n\t\t\t}\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\tw.Write(buf.Bytes())\n\t\t\tcount++\n\t\t\treturn\n\t\t}\n\t\tw.WriteHeader(http.StatusOK)\n\t\tjson.NewEncoder(w).Encode(&stmtResponse{\n\t\t\tError: ErrTrino{\n\t\t\t\tErrorName: \"TEST\",\n\t\t\t},\n\t\t})\n\t}))\n\n\tdb, err := sql.Open(\"trino\", ts.URL)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\t_, err = db.Query(\"SELECT 1\")\n\tassert.IsTypef(t, new(ErrQueryFailed), err, \"unexpected error: %w\", err)\n\n}\n\nfunc TestSpoolingProtocolSpooledSegmentDecoders(t *testing.T) {\n\ttestcases := []struct {\n\t\tName           string\n\t\tSegments       []map[string]interface{}\n\t\tExpectedResult []int\n\t\tEncoding       string\n\t\tDownloadedData []byte\n\t}{\n\t\t{\n\t\t\tName: \"noCompression\",\n\t\t\tSegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"spooled\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"segmentSize\": 16, \"rowOffset\": 0, \"rowsCount\": 2},\n\t\t\t\t\t\"ackUri\":   \"test\",\n\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tEncoding:       \"json\",\n\t\t\tExpectedResult: []int{1000, 10001},\n\t\t\tDownloadedData: []byte(\"[[1000],[10001]]\"),\n\t\t},\n\t\t{\n\t\t\tName: \"zstdCompression\",\n\t\t\tSegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"spooled\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"uncompressedSize\": 16, \"rowOffset\": 0, \"segmentSize\": 29},\n\t\t\t\t\t\"ackUri\":   \"test\",\n\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tEncoding:       \"json+zstd\",\n\t\t\tExpectedResult: []int{1000, 10001},\n\t\t\tDownloadedData: mustDecodeBase64(\"KLUv/QQAgQAAW1sxMDAwXSxbMTAwMDFdXZfUttw=\"),\n\t\t},\n\t\t{\n\t\t\tName: \"spooledSegmentWithoutHeadersOnReponse\", // headers are optional\n\t\t\tSegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"spooled\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"uncompressedSize\": 16, \"rowOffset\": 0, \"segmentSize\": 29},\n\t\t\t\t\t\"ackUri\":   \"test\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tEncoding:       \"json+zstd\",\n\t\t\tExpectedResult: []int{1000, 10001},\n\t\t\tDownloadedData: mustDecodeBase64(\"KLUv/QQAgQAAW1sxMDAwXSxbMTAwMDFdXZfUttw=\"),\n\t\t},\n\t\t{\n\t\t\tName: \"zlibCompression\",\n\t\t\tSegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"spooled\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"uncompressedSize\": 16, \"rowOffset\": 0, \"segmentSize\": 18},\n\t\t\t\t\t\"ackUri\":   \"test\",\n\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tEncoding:       \"json+lz4\",\n\t\t\tExpectedResult: []int{1000, 10001},\n\t\t\tDownloadedData: mustDecodeBase64(\"8AFbWzEwMDBdLFsxMDAwMV1d\"),\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tvar ts *httptest.Server\n\t\t\tts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif r.URL.Path == \"/v1/statement\" {\n\t\t\t\t\tjson.NewEncoder(w).Encode(&stmtResponse{\n\t\t\t\t\t\tID:      \"fake-query\",\n\t\t\t\t\t\tNextURI: ts.URL + \"/v1/statement/20210817_140827_00000_arvdv/1\",\n\t\t\t\t\t})\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif r.URL.Path == \"/v1/statement/20210817_140827_00000_arvdv/1\" {\n\t\t\t\t\tjson.NewEncoder(w).Encode(&queryResponse{\n\t\t\t\t\t\tID: \"fake-query\",\n\t\t\t\t\t\tColumns: []queryColumn{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"_col0\",\n\t\t\t\t\t\t\t\tType: \"integer\",\n\t\t\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\t\t\tRawType:   \"integer\",\n\t\t\t\t\t\t\t\t\tArguments: []typeArgument{},\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\tData: map[string]interface{}{\n\t\t\t\t\t\t\t\"encoding\": tc.Encoding,\n\t\t\t\t\t\t\t\"segments\": tc.Segments,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif r.URL.Path == \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc=\" {\n\t\t\t\t\tw.Write(tc.DownloadedData)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\tjson.NewEncoder(w).Encode(ErrTrino{ErrorName: \"Unexpected request\"})\n\t\t\t}))\n\n\t\t\tdefer ts.Close()\n\n\t\t\ttc.Segments[0][\"uri\"] = ts.URL + \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc=\"\n\n\t\t\tdb, err := sql.Open(\"trino\", ts.URL)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer db.Close()\n\n\t\t\trows, err := db.Query(\"SELECT 1\")\n\t\t\trequire.NoError(t, err)\n\n\t\t\tvar results []int\n\t\t\tfor rows.Next() {\n\t\t\t\tvar value int\n\t\t\t\terr := rows.Scan(&value)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tresults = append(results, value)\n\t\t\t}\n\n\t\t\trequire.NoError(t, rows.Err())\n\n\t\t\tassert.Equal(t, tc.ExpectedResult, results, \"Expected query results to match\")\n\t\t})\n\t}\n}\n\nfunc TestSpoolingProtocolToManyOutOfOrderSegmentDownload(t *testing.T) {\n\tsegments := []map[string]interface{}{\n\t\t{\n\t\t\t\"type\":     \"spooled\",\n\t\t\t\"metadata\": map[string]interface{}{\"segmentSize\": 8, \"rowOffset\": 30, \"rowsCount\": 1},\n\t\t\t\"ackUri\":   \"test\",\n\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"type\":     \"spooled\",\n\t\t\t\"metadata\": map[string]interface{}{\"segmentSize\": 8, \"rowOffset\": 20, \"rowsCount\": 1},\n\t\t\t\"ackUri\":   \"test\",\n\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"type\":     \"spooled\",\n\t\t\t\"metadata\": map[string]interface{}{\"segmentSize\": 8, \"rowOffset\": 40, \"rowsCount\": 1},\n\t\t\t\"ackUri\":   \"test\",\n\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tvar ts *httptest.Server\n\tts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tswitch r.URL.Path {\n\t\tcase \"/v1/statement\":\n\t\t\tjson.NewEncoder(w).Encode(&stmtResponse{\n\t\t\t\tID:      \"fake-query\",\n\t\t\t\tNextURI: ts.URL + \"/v1/statement/20210817_140827_00000_arvdv/1\",\n\t\t\t})\n\t\t\treturn\n\n\t\tcase \"/v1/statement/20210817_140827_00000_arvdv/1\":\n\t\t\tjson.NewEncoder(w).Encode(&queryResponse{\n\t\t\t\tID: \"fake-query\",\n\t\t\t\tColumns: []queryColumn{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"_col0\",\n\t\t\t\t\t\tType: \"integer\",\n\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\tRawType:   \"integer\",\n\t\t\t\t\t\t\tArguments: []typeArgument{},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: map[string]interface{}{\n\t\t\t\t\t\"encoding\": \"json\",\n\t\t\t\t\t\"segments\": segments,\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn\n\n\t\tcase \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc=\":\n\t\t\tw.Write([]byte(\"[[1000]]\"))\n\t\t\treturn\n\n\t\tcase \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc1=\":\n\t\t\tw.Write([]byte(\"[[1001]]\"))\n\n\t\t\treturn\n\n\t\tcase \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc2=\":\n\t\t\tw.Write([]byte(\"[[1002]]\"))\n\n\t\t\treturn\n\n\t\tdefault:\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\tjson.NewEncoder(w).Encode(ErrTrino{ErrorName: \"Unexpected request\"})\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\t// Inject segment URIs into the segment definitions\n\tsegments[0][\"uri\"] = ts.URL + \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc=\"\n\tsegments[1][\"uri\"] = ts.URL + \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc1=\"\n\tsegments[2][\"uri\"] = ts.URL + \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc2=\"\n\n\tdb, err := sql.Open(\"trino\", ts.URL)\n\trequire.NoError(t, err)\n\tdefer db.Close()\n\n\trows, err := db.Query(\"SELECT 1\", sql.Named(trinoMaxOutOfOrdersSegments, \"3\"), sql.Named(trinoSpoolingWorkerCount, \"2\"))\n\trequire.NoError(t, err)\n\n\tfor rows.Next() {\n\t\tvar value int\n\t\terr := rows.Scan(&value)\n\t\trequire.NoError(t, err)\n\t}\n\n\trequire.Error(t, rows.Err())\n\n\trequire.ErrorContains(t, rows.Err(), \"all 3 out-of-order segments buffered (limit: 3). This indicates a bug or inconsistency in the segments metadata response (e.g., missing, duplicate, or misordered segments, or row offsets not matching the expected sequence)\")\n}\n\nfunc TestSpoolingProtocolOutOfOrderSegment(t *testing.T) {\n\t// Define the segments\n\tsegments := []map[string]interface{}{\n\t\t{\n\t\t\t\"type\":     \"spooled\",\n\t\t\t\"metadata\": map[string]interface{}{\"segmentSize\": 8, \"rowOffset\": 2, \"rowsCount\": 1},\n\t\t\t\"ackUri\":   \"test\",\n\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"type\":     \"spooled\",\n\t\t\t\"metadata\": map[string]interface{}{\"segmentSize\": 8, \"rowOffset\": 1, \"rowsCount\": 1},\n\t\t\t\"ackUri\":   \"test\",\n\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"type\":     \"spooled\",\n\t\t\t\"metadata\": map[string]interface{}{\"segmentSize\": 8, \"rowOffset\": 0, \"rowsCount\": 1},\n\t\t\t\"ackUri\":   \"test\",\n\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tvar ts *httptest.Server\n\tts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tswitch r.URL.Path {\n\t\tcase \"/v1/statement\":\n\t\t\tjson.NewEncoder(w).Encode(&stmtResponse{\n\t\t\t\tID:      \"fake-query\",\n\t\t\t\tNextURI: ts.URL + \"/v1/statement/20210817_140827_00000_arvdv/1\",\n\t\t\t})\n\t\t\treturn\n\n\t\tcase \"/v1/statement/20210817_140827_00000_arvdv/1\":\n\t\t\tjson.NewEncoder(w).Encode(&queryResponse{\n\t\t\t\tID: \"fake-query\",\n\t\t\t\tColumns: []queryColumn{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"_col0\",\n\t\t\t\t\t\tType: \"integer\",\n\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\tRawType:   \"integer\",\n\t\t\t\t\t\t\tArguments: []typeArgument{},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: map[string]interface{}{\n\t\t\t\t\t\"encoding\": \"json\",\n\t\t\t\t\t\"segments\": segments,\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn\n\n\t\tcase \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc=\":\n\t\t\tw.Write([]byte(\"[[1000]]\"))\n\t\t\treturn\n\n\t\tcase \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc1=\":\n\t\t\tw.Write([]byte(\"[[1001]]\"))\n\n\t\t\treturn\n\n\t\tcase \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc2=\":\n\t\t\tw.Write([]byte(\"[[1002]]\"))\n\n\t\t\treturn\n\n\t\tdefault:\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\tjson.NewEncoder(w).Encode(ErrTrino{ErrorName: \"Unexpected request\"})\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\t// Inject segment URIs into the segment definitions\n\tsegments[2][\"uri\"] = ts.URL + \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc=\"\n\tsegments[1][\"uri\"] = ts.URL + \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc1=\"\n\tsegments[0][\"uri\"] = ts.URL + \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc2=\"\n\n\tdb, err := sql.Open(\"trino\", ts.URL)\n\trequire.NoError(t, err)\n\tdefer db.Close()\n\n\trows, err := db.Query(\"SELECT 1\", sql.Named(trinoMaxOutOfOrdersSegments, \"3\"), sql.Named(trinoSpoolingWorkerCount, \"1\"))\n\trequire.NoError(t, err)\n\n\tvar results []int\n\tfor rows.Next() {\n\t\tvar value int\n\t\terr := rows.Scan(&value)\n\t\trequire.NoError(t, err)\n\t\tresults = append(results, value)\n\t}\n\n\trequire.NoError(t, rows.Err())\n\n\texpected := []int{1000, 1001, 1002}\n\tassert.Equal(t, expected, results, \"Expected query results to match\")\n}\n\nfunc TestSpoolingProtocolSegmentDownloadRetryFails(t *testing.T) {\n\ttestcases := []struct {\n\t\tName              string\n\t\tExpectedErrorMsg  string\n\t\tSimulateTimeout   bool\n\t\tHttpStatusReponse int\n\t}{\n\t\t{\n\t\t\tName:              \"Test retry 502 Bad Gateway\",\n\t\t\tHttpStatusReponse: http.StatusBadGateway,\n\t\t},\n\t\t{\n\t\t\tName:              \"Test retry 503 Service Unavailable\",\n\t\t\tHttpStatusReponse: http.StatusServiceUnavailable,\n\t\t},\n\t\t{\n\t\t\tName:              \"Test retry 504 Gateway Timeout\",\n\t\t\tHttpStatusReponse: http.StatusGatewayTimeout,\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tvar ts *httptest.Server\n\t\t\tvar failCounter int\n\t\t\tts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif r.URL.Path == \"/v1/statement\" {\n\t\t\t\t\tjson.NewEncoder(w).Encode(&stmtResponse{\n\t\t\t\t\t\tID:      \"fake-query\",\n\t\t\t\t\t\tNextURI: ts.URL + \"/v1/statement/20210817_140827_00000_arvdv/1\",\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif r.URL.Path == \"/v1/statement/20210817_140827_00000_arvdv/1\" {\n\t\t\t\t\tjson.NewEncoder(w).Encode(&queryResponse{\n\t\t\t\t\t\tID: \"fake-query\",\n\t\t\t\t\t\tColumns: []queryColumn{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"_col0\",\n\t\t\t\t\t\t\t\tType: \"integer\",\n\t\t\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\t\t\tRawType:   \"integer\",\n\t\t\t\t\t\t\t\t\tArguments: []typeArgument{},\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\tData: map[string]interface{}{\n\t\t\t\t\t\t\t\"encoding\": \"json\",\n\t\t\t\t\t\t\t\"segments\": []map[string]interface{}{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"uri\":      ts.URL + \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc=\",\n\t\t\t\t\t\t\t\t\t\"type\":     \"spooled\",\n\t\t\t\t\t\t\t\t\t\"metadata\": map[string]interface{}{\"segmentSize\": 325, \"rowOffset\": 0, \"rowsCount\": 1},\n\t\t\t\t\t\t\t\t\t\"ackUri\":   \"test\",\n\t\t\t\t\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\t\t\t\t\"test\": []interface{}{\"test\"},\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\treturn\n\t\t\t\t}\n\t\t\t\tif r.URL.Path == \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc=\" {\n\t\t\t\t\tif failCounter < 2 {\n\t\t\t\t\t\tfailCounter++\n\t\t\t\t\t\tw.WriteHeader(tc.HttpStatusReponse)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t\t\tw.Write([]byte(\"[[1000]]\"))\n\t\t\t\t}\n\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\tjson.NewEncoder(w).Encode(ErrTrino{ErrorName: \"Unexpected request\"})\n\t\t\t}))\n\t\t\tdefer ts.Close()\n\n\t\t\tdb, err := sql.Open(\"trino\", ts.URL)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer db.Close()\n\n\t\t\trows, err := db.Query(\"SELECT 1\")\n\t\t\trequire.NoError(t, err)\n\n\t\t\tvar results []int\n\t\t\tfor rows.Next() {\n\t\t\t\tvar value int\n\t\t\t\terr := rows.Scan(&value)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tresults = append(results, value)\n\t\t\t}\n\n\t\t\trequire.NoError(t, rows.Err())\n\n\t\t\tassert.Equal(t, []int{1000}, results, \"Expected query results to match\")\n\t\t\tassert.Equal(t, 2, failCounter, \"Expected segment download to fail exactly 2 times before succeeding\")\n\t\t})\n\t}\n}\n\nfunc TestSpoolingProtocolSegmentDownloadRetryMaxAttempts(t *testing.T) {\n\tvar ts *httptest.Server\n\tfailCounter := 0\n\tmaxRetries := 6\n\n\tts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tswitch r.URL.Path {\n\t\tcase \"/v1/statement\":\n\t\t\tjson.NewEncoder(w).Encode(&stmtResponse{\n\t\t\t\tID:      \"fake-query\",\n\t\t\t\tNextURI: ts.URL + \"/v1/statement/20210817_140827_00000_arvdv/1\",\n\t\t\t})\n\t\tcase \"/v1/statement/20210817_140827_00000_arvdv/1\":\n\t\t\tjson.NewEncoder(w).Encode(&queryResponse{\n\t\t\t\tID: \"fake-query\",\n\t\t\t\tColumns: []queryColumn{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"_col0\",\n\t\t\t\t\t\tType: \"integer\",\n\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\tRawType:   \"integer\",\n\t\t\t\t\t\t\tArguments: []typeArgument{},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: map[string]interface{}{\n\t\t\t\t\t\"encoding\": \"json\",\n\t\t\t\t\t\"segments\": []map[string]interface{}{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"uri\":      ts.URL + \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc=\",\n\t\t\t\t\t\t\t\"type\":     \"spooled\",\n\t\t\t\t\t\t\t\"metadata\": map[string]interface{}{\"segmentSize\": 325, \"rowOffset\": 0, \"rowsCount\": 1},\n\t\t\t\t\t\t\t\"ackUri\":   \"test\",\n\t\t\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\t\t\"test\": []interface{}{\"test\"},\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\tcase \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc=\":\n\t\t\tif failCounter <= maxRetries {\n\t\t\t\tfailCounter++\n\t\t\t\tw.WriteHeader(http.StatusBadGateway)\n\t\t\t\treturn\n\t\t\t}\n\t\tdefault:\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\tjson.NewEncoder(w).Encode(ErrTrino{ErrorName: \"Unexpected request\"})\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\tdb, err := sql.Open(\"trino\", ts.URL)\n\trequire.NoError(t, err)\n\tdefer db.Close()\n\n\trows, err := db.Query(\"SELECT 1\")\n\trequire.NoError(t, err)\n\n\tfor rows.Next() {\n\t}\n\n\trequire.Error(t, rows.Err())\n\n\trequire.ErrorContains(t, rows.Err(), \"max retries reached for status code 502\")\n\tassert.Equal(t, maxRetries, failCounter, \"Expected segment download to fail exactly 5 times before succeeding\")\n}\n\nfunc mustDecodeBase64(encoded string) []byte {\n\tdata, err := base64.StdEncoding.DecodeString(encoded)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"Failed to decode base64: %v\", err))\n\t}\n\treturn data\n}\n\nfunc TestSpoolingProtocolOnlyWithInlineSegments(t *testing.T) {\n\tvar ts *httptest.Server\n\n\tts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.URL.Path == \"/v1/statement\" {\n\t\t\tjson.NewEncoder(w).Encode(&stmtResponse{\n\t\t\t\tID:      \"fake-query\",\n\t\t\t\tNextURI: ts.URL + \"/v1/statement/20210817_140827_00000_arvdv/1\",\n\t\t\t})\n\n\t\t\treturn\n\t\t}\n\t\tif r.URL.Path == \"/v1/statement/20210817_140827_00000_arvdv/1\" {\n\t\t\tjson.NewEncoder(w).Encode(&queryResponse{\n\t\t\t\tID: \"fake-query\",\n\t\t\t\tColumns: []queryColumn{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"_col0\",\n\t\t\t\t\t\tType: \"integer\",\n\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\tRawType:   \"integer\",\n\t\t\t\t\t\t\tArguments: []typeArgument{},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: map[string]interface{}{\n\t\t\t\t\t\"encoding\": \"json\",\n\t\t\t\t\t\"segments\": []map[string]interface{}{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"type\":     \"inline\",\n\t\t\t\t\t\t\t\"data\":     \"W1sxMDAwXSwgWzEwMDAxXV0=\",\n\t\t\t\t\t\t\t\"metadata\": map[string]interface{}{\"segmentSize\": 17, \"rowOffset\": 0},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"type\":     \"inline\",\n\t\t\t\t\t\t\t\"data\":     \"W1sxMDAwXSwgWzEwMDAxXV0=\",\n\t\t\t\t\t\t\t\"metadata\": map[string]interface{}{\"segmentSize\": 17, \"rowOffset\": 2},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"type\":     \"inline\",\n\t\t\t\t\t\t\t\"data\":     \"W1sxMDAwXSwgWzEwMDAxXV0=\",\n\t\t\t\t\t\t\t\"metadata\": map[string]interface{}{\"segmentSize\": 17, \"rowOffset\": 4},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"type\":     \"inline\",\n\t\t\t\t\t\t\t\"data\":     \"W1sxMDAwXSwgWzEwMDAxXV0=\",\n\t\t\t\t\t\t\t\"metadata\": map[string]interface{}{\"segmentSize\": 17, \"rowOffset\": 6},\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\treturn\n\t\t}\n\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\tjson.NewEncoder(w).Encode(ErrTrino{ErrorName: \"Unexpected request\"})\n\t}))\n\n\tdefer ts.Close()\n\n\tdb, err := sql.Open(\"trino\", ts.URL)\n\trequire.NoError(t, err)\n\tdefer db.Close()\n\n\trows, err := db.Query(\"SELECT 1\", sql.Named(trinoSpoolingWorkerCount, \"2\"), sql.Named(trinoMaxOutOfOrdersSegments, \"2\"))\n\trequire.NoError(t, err)\n\n\tvar results []int\n\tfor rows.Next() {\n\t\tvar value int\n\t\terr := rows.Scan(&value)\n\t\trequire.NoError(t, err)\n\t\tresults = append(results, value)\n\t}\n\n\trequire.NoError(t, rows.Err())\n\n\tassert.Equal(t, []int{1000, 10001, 1000, 10001, 1000, 10001, 1000, 10001}, results, \"Expected query results to match\")\n}\n\nfunc TestSpoolingProtocolInlineSegmentDecoders(t *testing.T) {\n\ttestcases := []struct {\n\t\tName           string\n\t\tSegments       []map[string]interface{}\n\t\tExpectedResult []int\n\t\tEncoding       string\n\t}{\n\t\t{\n\t\t\tName: \"noCompression\",\n\t\t\tSegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"inline\",\n\t\t\t\t\t\"data\":     \"W1sxMDAwXSwgWzEwMDAxXV0=\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"segmentSize\": 17, \"rowOffset\": 0},\n\t\t\t\t},\n\t\t\t},\n\t\t\tEncoding:       \"json\",\n\t\t\tExpectedResult: []int{1000, 10001},\n\t\t},\n\t\t{\n\t\t\tName: \"zstdCompression\",\n\t\t\tSegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"inline\",\n\t\t\t\t\t\"data\":     \"KLUv/QQAgQAAW1sxMDAwXSxbMTAwMDFdXZfUttw=\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"uncompressedSize\": 16, \"rowOffset\": 0, \"segmentSize\": 29},\n\t\t\t\t},\n\t\t\t},\n\t\t\tEncoding:       \"json+zstd\",\n\t\t\tExpectedResult: []int{1000, 10001},\n\t\t},\n\t\t{\n\t\t\tName: \"zlibCompression\",\n\t\t\tSegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"inline\",\n\t\t\t\t\t\"data\":     \"8AFbWzEwMDBdLFsxMDAwMV1d\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"uncompressedSize\": 16, \"rowOffset\": 0, \"segmentSize\": 18},\n\t\t\t\t},\n\t\t\t},\n\t\t\tEncoding:       \"json+lz4\",\n\t\t\tExpectedResult: []int{1000, 10001},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tvar ts *httptest.Server\n\n\t\t\tts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif r.URL.Path == \"/v1/statement\" {\n\t\t\t\t\tjson.NewEncoder(w).Encode(&stmtResponse{\n\t\t\t\t\t\tID:      \"fake-query\",\n\t\t\t\t\t\tNextURI: ts.URL + \"/v1/statement/20210817_140827_00000_arvdv/1\",\n\t\t\t\t\t})\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif r.URL.Path == \"/v1/statement/20210817_140827_00000_arvdv/1\" {\n\t\t\t\t\tjson.NewEncoder(w).Encode(&queryResponse{\n\t\t\t\t\t\tID: \"fake-query\",\n\t\t\t\t\t\tColumns: []queryColumn{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"_col0\",\n\t\t\t\t\t\t\t\tType: \"integer\",\n\t\t\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\t\t\tRawType:   \"integer\",\n\t\t\t\t\t\t\t\t\tArguments: []typeArgument{},\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\tData: map[string]interface{}{\n\t\t\t\t\t\t\t\"encoding\": tc.Encoding,\n\t\t\t\t\t\t\t\"segments\": tc.Segments,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\tjson.NewEncoder(w).Encode(ErrTrino{ErrorName: \"Unexpected request\"})\n\t\t\t}))\n\n\t\t\tdefer ts.Close()\n\n\t\t\tdb, err := sql.Open(\"trino\", ts.URL)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer db.Close()\n\n\t\t\trows, err := db.Query(\"SELECT 1\")\n\t\t\trequire.NoError(t, err)\n\n\t\t\tvar results []int\n\t\t\tfor rows.Next() {\n\t\t\t\tvar value int\n\t\t\t\terr := rows.Scan(&value)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tresults = append(results, value)\n\t\t\t}\n\n\t\t\trequire.NoError(t, rows.Err())\n\n\t\t\tassert.Equal(t, tc.ExpectedResult, results, \"Expected query results to match\")\n\t\t})\n\t}\n}\n\nfunc TestSpoolingProtocolSpooledSegmentErrorHandling(t *testing.T) {\n\ttestcases := []struct {\n\t\tname                          string\n\t\tsegments                      []map[string]interface{}\n\t\texpectedError                 string\n\t\tdownloadedData                []byte\n\t\tdownloadedDataStatusCodeError bool\n\t}{\n\t\t{\n\t\t\tname: \"MissingRowOffsetMetadata\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"spooled\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"uncompressedSize\": 2, \"segmentSize\": 11},\n\t\t\t\t\t\"ackUri\":   \"test\",\n\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: \"rowOffset is missing in segment metadata\",\n\t\t},\n\t\t{\n\t\t\tname: \"WrongRowOffsetMetadataType\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"spooled\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"uncompressedSize\": 2, \"rowOffset\": \"2\", \"segmentSize\": 11},\n\t\t\t\t\t\"ackUri\":   \"test\",\n\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: \"invalid type for rowOffset in segment metadata, expected json.Number\",\n\t\t},\n\t\t{\n\t\t\tname: \"MissingSegmentSizeMetadata\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"spooled\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"uncompressedSize\": 2, \"rowOffset\": 2},\n\t\t\t\t\t\"ackUri\":   \"test\",\n\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: \"segmentSize is missing in segment metadata\",\n\t\t},\n\t\t{\n\t\t\tname: \"WrongSegmentSizeMetadataType\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"spooled\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"uncompressedSize\": 2, \"rowOffset\": 2, \"segmentSize\": \"11\"},\n\t\t\t\t\t\"ackUri\":   \"test\",\n\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: \"invalid type for segmentSize in segment metadata, expected json.Number\",\n\t\t},\n\t\t{\n\t\t\tname: \"MissingMetadata\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":   \"spooled\",\n\t\t\t\t\t\"ackUri\": \"test\",\n\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: \"metadata is missing in segment at index 0\",\n\t\t},\n\t\t{\n\t\t\tname: \"WrongMetadataType\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"spooled\",\n\t\t\t\t\t\"metadata\": \"fake-metadata\",\n\t\t\t\t\t\"ackUri\":   \"test\",\n\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: \"metadata is invalid or cannot be parsed as map[string]interface{} in segment at index 0\",\n\t\t},\n\t\t{\n\t\t\tname: \"WrongUncompressSize\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"spooled\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"uncompressedSize\": 2, \"rowOffset\": 2, \"segmentSize\": 11},\n\t\t\t\t\t\"ackUri\":   \"test\",\n\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError:  \"failed to decode spooled segment at index 0: segment size mismatch: expected 11 bytes, got 29 byte\",\n\t\t\tdownloadedData: mustDecodeBase64(\"KLUv/QQAgQAAW1sxMDAwXSxbMTAwMDFdXZfUttw=\"),\n\t\t},\n\t\t{\n\t\t\tname: \"WrongCompresSize\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"spooled\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"uncompressedSize\": 2, \"rowOffset\": 2, \"segmentSize\": 29},\n\t\t\t\t\t\"ackUri\":   \"test\",\n\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError:  \"decompressed size mismatch: expected 2 bytes, got 16 bytes\",\n\t\t\tdownloadedData: mustDecodeBase64(\"KLUv/QQAgQAAW1sxMDAwXSxbMTAwMDFdXZfUttw=\"),\n\t\t},\n\t\t{\n\t\t\tname: \"MissingUri\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":   \"spooled\",\n\t\t\t\t\t\"data\":   \"fake-data\",\n\t\t\t\t\t\"ackUri\": \"test\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\t\"segmentSize\":      3679,\n\t\t\t\t\t\t\"uncompressedSize\": 2,\n\t\t\t\t\t\t\"rowOffset\":        0,\n\t\t\t\t\t},\n\t\t\t\t\t\"headers\": map[string][]interface{}{\n\t\t\t\t\t\t\"x-amz-server-side-encryption-customer-algorithm\": {\"AES256\"},\n\t\t\t\t\t\t\"x-amz-server-side-encryption-customer-key\":       {\"key\"},\n\t\t\t\t\t\t\"x-amz-server-side-encryption-customer-key-md5\":   {\"md5\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: \"missing or invalid 'uri' field in spooled segment at index 0\",\n\t\t},\n\t\t{\n\t\t\tname: \"MissingUriAck\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"spooled\",\n\t\t\t\t\t\"data\": \"fake-data\",\n\t\t\t\t\t\"uri\":  \"fake-uri\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\t\"segmentSize\":      3679,\n\t\t\t\t\t\t\"uncompressedSize\": 2,\n\t\t\t\t\t\t\"rowOffset\":        0,\n\t\t\t\t\t},\n\t\t\t\t\t\"headers\": map[string][]interface{}{\n\t\t\t\t\t\t\"x-amz-server-side-encryption-customer-algorithm\": {\"AES256\"},\n\t\t\t\t\t\t\"x-amz-server-side-encryption-customer-key\":       {\"key\"},\n\t\t\t\t\t\t\"x-amz-server-side-encryption-customer-key-md5\":   {\"md5\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: \"missing or invalid 'ackUri' field in spooled segment at index 0\",\n\t\t},\n\t\t{\n\t\t\tname: \"wrongHeadersFormat\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":   \"spooled\",\n\t\t\t\t\t\"data\":   \"fake-data\",\n\t\t\t\t\t\"uri\":    \"fake-uri\",\n\t\t\t\t\t\"ackUri\": \"test\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\t\"segmentSize\":      3679,\n\t\t\t\t\t\t\"uncompressedSize\": 2,\n\t\t\t\t\t\t\"rowOffset\":        0,\n\t\t\t\t\t},\n\t\t\t\t\t\"headers\": [][]string{\n\t\t\t\t\t\t{\"x-amz-server-side-encryption-customer-algorithm\", \"AES256\"},\n\t\t\t\t\t\t{\"x-amz-server-side-encryption-customer-key\", \"key\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: \"invalid 'headers' field in spooled segment at index 0: expected map[string]interface{}\",\n\t\t},\n\t\t{\n\t\t\tname: \"HeadersWithMultipleValues\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":   \"spooled\",\n\t\t\t\t\t\"data\":   \"fake-data\",\n\t\t\t\t\t\"uri\":    \"fake-uri\",\n\t\t\t\t\t\"ackUri\": \"test\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\t\"segmentSize\":      3679,\n\t\t\t\t\t\t\"uncompressedSize\": 2,\n\t\t\t\t\t\t\"rowOffset\":        0,\n\t\t\t\t\t},\n\t\t\t\t\t\"headers\": map[string][]interface{}{\n\t\t\t\t\t\t\"x-amz-server-side-encryption-customer-algorithm\": {\"AES256\"},\n\t\t\t\t\t\t\"x-amz-server-side-encryption-customer-key\":       {\"key\"},\n\t\t\t\t\t\t\"x-amz-server-side-encryption-customer-key-md5\":   {\"md5\", \"md5\"}, // wrong, more then one\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: \"multiple values for header x-amz-server-side-encryption-customer-key-md5\",\n\t\t},\n\t\t{\n\t\t\tname: \"HeaderValueWrongType\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":   \"spooled\",\n\t\t\t\t\t\"data\":   \"fake-data\",\n\t\t\t\t\t\"uri\":    \"fake-uri\",\n\t\t\t\t\t\"ackUri\": \"test\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\t\"segmentSize\":      3679,\n\t\t\t\t\t\t\"uncompressedSize\": 2,\n\t\t\t\t\t\t\"rowOffset\":        0,\n\t\t\t\t\t},\n\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\"x-amz-server-side-encryption-customer-algorithm\": []interface{}{\"AES256\"},\n\t\t\t\t\t\t\"x-amz-server-side-encryption-customer-key\":       []interface{}{\"key\"},\n\t\t\t\t\t\t\"x-amz-server-side-encryption-customer-key-md5\":   []interface{}{123}, // Wrong type: integer instead of string\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: \"unsupported header value type json.Number\",\n\t\t},\n\t\t{\n\t\t\tname: \"HeaderTypeInvalid\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":   \"spooled\",\n\t\t\t\t\t\"data\":   \"fake-data\",\n\t\t\t\t\t\"uri\":    \"fake-uri\",\n\t\t\t\t\t\"ackUri\": \"test\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\t\"segmentSize\":      3679,\n\t\t\t\t\t\t\"uncompressedSize\": 2,\n\t\t\t\t\t\t\"rowOffset\":        0,\n\t\t\t\t\t},\n\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\"x-amz-server-side-encryption-customer-algorithm\": \"AES256\", // Invalid type: string instead of []interface{}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: \"unsupported header type string\",\n\t\t},\n\t\t{\n\t\t\tname: \"ErrorDownloadingSegment\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"spooled\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"uncompressedSize\": 2, \"rowOffset\": 2, \"segmentSize\": 11},\n\t\t\t\t\t\"ackUri\":   \"test\",\n\t\t\t\t\t\"headers\": map[string]interface{}{\n\t\t\t\t\t\t\"test\": []interface{}{\"test\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError:                 \"trino: query failed (500 Internal Server Error):\",\n\t\t\tdownloadedData:                mustDecodeBase64(\"KLUv/QQAgQAAW1sxMDAwXSxbMTAwMDFdXZfUttw=\"),\n\t\t\tdownloadedDataStatusCodeError: true,\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tvar ts *httptest.Server\n\n\t\t\tts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif r.URL.Path == \"/v1/statement\" {\n\t\t\t\t\tjson.NewEncoder(w).Encode(&stmtResponse{\n\t\t\t\t\t\tID:      \"fake-query\",\n\t\t\t\t\t\tNextURI: ts.URL + \"/v1/statement/20210817_140827_00000_arvdv/1\",\n\t\t\t\t\t})\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif r.URL.Path == \"/v1/statement/20210817_140827_00000_arvdv/1\" {\n\t\t\t\t\tjson.NewEncoder(w).Encode(&queryResponse{\n\t\t\t\t\t\tID: \"fake-query\",\n\t\t\t\t\t\tColumns: []queryColumn{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"_col0\",\n\t\t\t\t\t\t\t\tType: \"integer\",\n\t\t\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\t\t\tRawType:   \"integer\",\n\t\t\t\t\t\t\t\t\tArguments: []typeArgument{},\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\tData: map[string]interface{}{\n\t\t\t\t\t\t\t\"encoding\": \"json+zstd\",\n\t\t\t\t\t\t\t\"segments\": tc.segments,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif r.URL.Path == \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc=\" {\n\t\t\t\t\tif tc.downloadedDataStatusCodeError {\n\t\t\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\t\t}\n\n\t\t\t\t\tw.Write(tc.downloadedData)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\tjson.NewEncoder(w).Encode(ErrTrino{ErrorName: \"Unexpected request\"})\n\t\t\t}))\n\n\t\t\tdefer ts.Close()\n\n\t\t\tif tc.name != \"MissingUri\" {\n\t\t\t\ttc.segments[0][\"uri\"] = ts.URL + \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc=\"\n\t\t\t}\n\n\t\t\tdb, err := sql.Open(\"trino\", ts.URL)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer db.Close()\n\n\t\t\trows, err := db.Query(\"SELECT 1\")\n\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer rows.Close()\n\n\t\t\tfor rows.Next() {\n\t\t\t\t// force segment processing\n\t\t\t}\n\n\t\t\terr = rows.Err()\n\t\t\trequire.Error(t, err)\n\t\t\trequire.Contains(t, err.Error(), tc.expectedError)\n\t\t})\n\t}\n}\n\nfunc TestSpoolingProtocolInlineSegmentErrorHandling(t *testing.T) {\n\ttestcases := []struct {\n\t\tname          string\n\t\tsegments      []map[string]interface{}\n\t\texpectedError string\n\t}{\n\t\t{\n\t\t\tname: \"WrongUncompressSize\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"inline\",\n\t\t\t\t\t\"data\":     \"KLUv/QQAgQAAW1sxMDAwXSxbMTAwMDFdXZfUttw=\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"uncompressedSize\": 1, \"rowOffset\": 2, \"segmentSize\": 29},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: \"failed to decode spooled segment at index 0: decompressed size mismatch: expected 1 bytes, got 16 bytes\",\n\t\t},\n\t\t{\n\t\t\tname: \"WrongCompresSize\",\n\t\t\tsegments: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"type\":     \"inline\",\n\t\t\t\t\t\"data\":     \"KLUv/QQAgQAAW1sxMDAwXSxbMTAwMDFdXZfUttw=\",\n\t\t\t\t\t\"metadata\": map[string]interface{}{\"uncompressedSize\": 16, \"rowOffset\": 2, \"segmentSize\": 1},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: \"failed to decode spooled segment at index 0: segment size mismatch: expected 1 bytes, got 29 bytes\",\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tvar ts *httptest.Server\n\n\t\t\tts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif r.URL.Path == \"/v1/statement\" {\n\t\t\t\t\tjson.NewEncoder(w).Encode(&stmtResponse{\n\t\t\t\t\t\tID:      \"fake-query\",\n\t\t\t\t\t\tNextURI: ts.URL + \"/v1/statement/20210817_140827_00000_arvdv/1\",\n\t\t\t\t\t})\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif r.URL.Path == \"/v1/statement/20210817_140827_00000_arvdv/1\" {\n\t\t\t\t\tjson.NewEncoder(w).Encode(&queryResponse{\n\t\t\t\t\t\tID: \"fake-query\",\n\t\t\t\t\t\tColumns: []queryColumn{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"_col0\",\n\t\t\t\t\t\t\t\tType: \"integer\",\n\t\t\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\t\t\tRawType:   \"integer\",\n\t\t\t\t\t\t\t\t\tArguments: []typeArgument{},\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\tData: map[string]interface{}{\n\t\t\t\t\t\t\t\"encoding\": \"json+zstd\",\n\t\t\t\t\t\t\t\"segments\": tc.segments,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\tjson.NewEncoder(w).Encode(ErrTrino{ErrorName: \"Unexpected request\"})\n\t\t\t}))\n\n\t\t\tdefer ts.Close()\n\n\t\t\tif tc.name != \"MissingUri\" {\n\t\t\t\ttc.segments[0][\"uri\"] = ts.URL + \"/v1/spooled/download/jKaLK0aVkNp2ixl6BOuwGMJ0nRjbUVKLHW_f3-I-1Cc=\"\n\t\t\t}\n\n\t\t\tdb, err := sql.Open(\"trino\", ts.URL)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer db.Close()\n\n\t\t\trows, err := db.Query(\"SELECT 1\")\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor rows.Next() {\n\t\t\t\t// force segment processing\n\t\t\t}\n\n\t\t\terr = rows.Err()\n\t\t\trequire.Error(t, err)\n\t\t\trequire.Contains(t, err.Error(), tc.expectedError)\n\t\t})\n\t}\n}\n\nfunc TestProtocolErrorHandling(t *testing.T) {\n\ttestcases := []struct {\n\t\tname          string\n\t\tdata          interface{}\n\t\texpectedError string\n\t}{\n\t\t{\n\t\t\tname: \"DirectProtocolInvalidRowType\",\n\t\t\tdata: []interface{}{\n\t\t\t\t123,\n\t\t\t},\n\t\t\texpectedError: \"unexpected data type for row at index 0: expected []interface{}, got json.Number\",\n\t\t},\n\t\t{\n\t\t\tname: \"SpoolingProtocolMissingEncoding\",\n\t\t\tdata: map[string]interface{}{\n\t\t\t\t\"segments\": []interface{}{}, // Missing \"encoding\" field\n\t\t\t},\n\t\t\texpectedError: \"invalid or missing 'encoding' field on spooling protocol, expected string\",\n\t\t},\n\t\t{\n\t\t\tname: \"SpoolingProtocolInvalidSegmentsType\",\n\t\t\tdata: map[string]interface{}{\n\t\t\t\t\"encoding\": \"json\",\n\t\t\t\t\"segments\": \"invalid\", // Invalid type for \"segments\"\n\t\t\t},\n\t\t\texpectedError: \"nvalid or missing 'segments' field on spooling protocol, expected []interface{}\",\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tvar ts *httptest.Server\n\n\t\t\tts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif r.URL.Path == \"/v1/statement\" {\n\t\t\t\t\tjson.NewEncoder(w).Encode(&stmtResponse{\n\t\t\t\t\t\tID:      \"fake-query\",\n\t\t\t\t\t\tNextURI: ts.URL + \"/v1/statement/20210817_140827_00000_arvdv/1\",\n\t\t\t\t\t})\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif r.URL.Path == \"/v1/statement/20210817_140827_00000_arvdv/1\" {\n\t\t\t\t\tjson.NewEncoder(w).Encode(&queryResponse{\n\t\t\t\t\t\tID: \"fake-query\",\n\t\t\t\t\t\tColumns: []queryColumn{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"_col0\",\n\t\t\t\t\t\t\t\tType: \"integer\",\n\t\t\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\t\t\tRawType:   \"integer\",\n\t\t\t\t\t\t\t\t\tArguments: []typeArgument{},\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\tData: tc.data,\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\tjson.NewEncoder(w).Encode(ErrTrino{ErrorName: \"Unexpected request\"})\n\t\t\t}))\n\n\t\t\tdefer ts.Close()\n\n\t\t\tdb, err := sql.Open(\"trino\", ts.URL)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer db.Close()\n\n\t\t\t_, err = db.Query(\"SELECT 1\")\n\t\t\trequire.Error(t, err)\n\t\t\trequire.Contains(t, err.Error(), tc.expectedError)\n\t\t})\n\t}\n}\n\nfunc TestSession(t *testing.T) {\n\tif testing.Short() {\n\t\tt.Skip(\"Skipping test in short mode.\")\n\t}\n\terr := RegisterCustomClient(\"uncompressed\", &http.Client{Transport: &http.Transport{DisableCompression: true}})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tc := &Config{\n\t\tServerURI:         *integrationServerFlag + \"?custom_client=uncompressed\",\n\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\tdb, err := sql.Open(\"trino\", dsn)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\t_, err = db.Exec(\"SET SESSION join_distribution_type='BROADCAST'\")\n\trequire.NoError(t, err, \"Failed executing query\")\n\n\trow := db.QueryRow(\"SHOW SESSION LIKE 'join_distribution_type'\")\n\tvar name string\n\tvar value string\n\tvar defaultValue string\n\tvar typeName string\n\tvar description string\n\terr = row.Scan(&name, &value, &defaultValue, &typeName, &description)\n\trequire.NoError(t, err, \"Failed executing query\")\n\n\tassert.Equal(t, \"BROADCAST\", value)\n\n\t_, err = db.Exec(\"RESET SESSION join_distribution_type\")\n\trequire.NoError(t, err, \"Failed executing query\")\n\n\trow = db.QueryRow(\"SHOW SESSION LIKE 'join_distribution_type'\")\n\terr = row.Scan(&name, &value, &defaultValue, &typeName, &description)\n\trequire.NoError(t, err, \"Failed executing query\")\n\n\tassert.Equal(t, \"AUTOMATIC\", value)\n}\n\nfunc TestSetRoleHeader(t *testing.T) {\n\tvar firstRoleHeader string\n\tvar secondRoleHeader string\n\tvar requestCount int\n\tvar baseURL string\n\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\trequestCount++\n\t\troleHeader := r.Header.Get(trinoRoleHeader)\n\n\t\tif r.URL.Path == \"/v1/statement\" {\n\t\t\t// Capture the initial role from DSN\n\t\t\tfirstRoleHeader = roleHeader\n\t\t\tw.Header().Set(trinoSetRoleHeader, \"ROLE%7Badmin%7D\")\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\tjson.NewEncoder(w).Encode(&stmtResponse{\n\t\t\t\tID:      \"query1\",\n\t\t\t\tNextURI: baseURL + \"/v1/statement/query1/1\",\n\t\t\t\tStats: stmtStats{\n\t\t\t\t\tState: \"RUNNING\",\n\t\t\t\t},\n\t\t\t})\n\t\t} else if r.URL.Path == \"/v1/statement/query1/1\" {\n\t\t\t// Capture the role in subsequent request(e.g after server set)\n\t\t\tsecondRoleHeader = roleHeader\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\tjson.NewEncoder(w).Encode(&queryResponse{\n\t\t\t\tID: \"query1\",\n\t\t\t\tStats: stmtStats{\n\t\t\t\t\tState: \"FINISHED\",\n\t\t\t\t},\n\t\t\t\tData: [][]interface{}{{1}},\n\t\t\t\tColumns: []queryColumn{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"_col0\",\n\t\t\t\t\t\tType: \"integer\",\n\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\tRawType:   \"integer\",\n\t\t\t\t\t\t\tArguments: []typeArgument{},\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 if r.Method == \"DELETE\" && r.URL.Path == \"/v1/query/query1\" {\n\t\t\tw.WriteHeader(http.StatusNoContent)\n\t\t} else {\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\tjson.NewEncoder(w).Encode(&queryResponse{\n\t\t\t\tID: \"query1\",\n\t\t\t\tStats: stmtStats{\n\t\t\t\t\tState: \"FINISHED\",\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t}))\n\tbaseURL = ts.URL\n\n\tt.Cleanup(ts.Close)\n\n\tdb, err := sql.Open(\"trino\", ts.URL+\"?roles=catalog%3Auser\")\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\trows, err := db.Query(\"SELECT 1\")\n\trequire.NoError(t, err)\n\trequire.NoError(t, rows.Close())\n\n\tassert.Equal(t, `catalog=ROLE{user}`, firstRoleHeader, \"initial role from DSN should be sent in first request\")\n\tassert.Equal(t, \"ROLE%7Badmin%7D\", secondRoleHeader, \"server-set role should be sent in subsequent requests\")\n\tassert.NotEqual(t, firstRoleHeader, secondRoleHeader, \"role should have changed from DSN value to server-set value\")\n}\n\nfunc TestUnsupportedHeader(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(trinoSetPathHeader, \"foo.bar\")\n\t\tw.WriteHeader(http.StatusOK)\n\t}))\n\n\tt.Cleanup(ts.Close)\n\n\tdb, err := sql.Open(\"trino\", ts.URL)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\t_, err = db.Query(\"SELECT 1\")\n\tassert.EqualError(t, err, ErrUnsupportedHeader.Error(), \"unexpected error\")\n}\n\nfunc TestSSLCertPath(t *testing.T) {\n\tdb, err := sql.Open(\"trino\", \"https://localhost:9?SSLCertPath=/tmp/invalid_test.cert\")\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\twant := \"Error loading SSL Cert File\"\n\terr = db.Ping()\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), want)\n}\n\nfunc TestWithoutSSLCertPath(t *testing.T) {\n\tdb, err := sql.Open(\"trino\", \"https://localhost:9\")\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\tassert.NoError(t, db.Ping())\n}\n\nfunc TestUnsupportedTransaction(t *testing.T) {\n\tdb, err := sql.Open(\"trino\", \"http://localhost:9\")\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\t_, err = db.Begin()\n\trequire.Error(t, err, \"unsupported transaction succeeded with no error\")\n\n\texpected := \"operation not supported\"\n\tassert.Contains(t, err.Error(), expected)\n}\n\nfunc TestTypeConversion(t *testing.T) {\n\tutc, err := time.LoadLocation(\"UTC\")\n\trequire.NoError(t, err)\n\tparis, err := time.LoadLocation(\"Europe/Paris\")\n\trequire.NoError(t, err)\n\n\ttestcases := []struct {\n\t\tDataType                   string\n\t\tRawType                    string\n\t\tArguments                  []typeArgument\n\t\tResponseUnmarshalledSample interface{}\n\t\tExpectedGoValue            interface{}\n\t}{\n\t\t{\n\t\t\tDataType:                   \"boolean\",\n\t\t\tRawType:                    \"boolean\",\n\t\t\tResponseUnmarshalledSample: true,\n\t\t\tExpectedGoValue:            true,\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"varchar(1)\",\n\t\t\tRawType:                    \"varchar\",\n\t\t\tResponseUnmarshalledSample: \"hello\",\n\t\t\tExpectedGoValue:            \"hello\",\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"bigint\",\n\t\t\tRawType:                    \"bigint\",\n\t\t\tResponseUnmarshalledSample: json.Number(\"1234516165077230279\"),\n\t\t\tExpectedGoValue:            int64(1234516165077230279),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"double\",\n\t\t\tRawType:                    \"double\",\n\t\t\tResponseUnmarshalledSample: json.Number(\"1.0\"),\n\t\t\tExpectedGoValue:            float64(1),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"date\",\n\t\t\tRawType:                    \"date\",\n\t\t\tResponseUnmarshalledSample: \"2017-07-10\",\n\t\t\tExpectedGoValue:            time.Date(2017, 7, 10, 0, 0, 0, 0, time.Local),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"time\",\n\t\t\tRawType:                    \"time\",\n\t\t\tResponseUnmarshalledSample: \"01:02:03.000\",\n\t\t\tExpectedGoValue:            time.Date(0, 1, 1, 1, 2, 3, 0, time.Local),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"time with time zone\",\n\t\t\tRawType:                    \"time with time zone\",\n\t\t\tResponseUnmarshalledSample: \"01:02:03.000 UTC\",\n\t\t\tExpectedGoValue:            time.Date(0, 1, 1, 1, 2, 3, 0, utc),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"time with time zone\",\n\t\t\tRawType:                    \"time with time zone\",\n\t\t\tResponseUnmarshalledSample: \"01:02:03.000 +03:00\",\n\t\t\tExpectedGoValue:            time.Date(0, 1, 1, 1, 2, 3, 0, time.FixedZone(\"\", 3*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"time with time zone\",\n\t\t\tRawType:                    \"time with time zone\",\n\t\t\tResponseUnmarshalledSample: \"01:02:03.000+03:00\",\n\t\t\tExpectedGoValue:            time.Date(0, 1, 1, 1, 2, 3, 0, time.FixedZone(\"\", 3*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"time with time zone\",\n\t\t\tRawType:                    \"time with time zone\",\n\t\t\tResponseUnmarshalledSample: \"01:02:03.000 -05:00\",\n\t\t\tExpectedGoValue:            time.Date(0, 1, 1, 1, 2, 3, 0, time.FixedZone(\"\", -5*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"time with time zone\",\n\t\t\tRawType:                    \"time with time zone\",\n\t\t\tResponseUnmarshalledSample: \"01:02:03.000-05:00\",\n\t\t\tExpectedGoValue:            time.Date(0, 1, 1, 1, 2, 3, 0, time.FixedZone(\"\", -5*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"time\",\n\t\t\tRawType:                    \"time\",\n\t\t\tResponseUnmarshalledSample: \"01:02:03.123456789\",\n\t\t\tExpectedGoValue:            time.Date(0, 1, 1, 1, 2, 3, 123456789, time.Local),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"time with time zone\",\n\t\t\tRawType:                    \"time with time zone\",\n\t\t\tResponseUnmarshalledSample: \"01:02:03.123456789 UTC\",\n\t\t\tExpectedGoValue:            time.Date(0, 1, 1, 1, 2, 3, 123456789, utc),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"time with time zone\",\n\t\t\tRawType:                    \"time with time zone\",\n\t\t\tResponseUnmarshalledSample: \"01:02:03.123456789 +03:00\",\n\t\t\tExpectedGoValue:            time.Date(0, 1, 1, 1, 2, 3, 123456789, time.FixedZone(\"\", 3*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"time with time zone\",\n\t\t\tRawType:                    \"time with time zone\",\n\t\t\tResponseUnmarshalledSample: \"01:02:03.123456789+03:00\",\n\t\t\tExpectedGoValue:            time.Date(0, 1, 1, 1, 2, 3, 123456789, time.FixedZone(\"\", 3*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"time with time zone\",\n\t\t\tRawType:                    \"time with time zone\",\n\t\t\tResponseUnmarshalledSample: \"01:02:03.123456789 -05:00\",\n\t\t\tExpectedGoValue:            time.Date(0, 1, 1, 1, 2, 3, 123456789, time.FixedZone(\"\", -5*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"time with time zone\",\n\t\t\tRawType:                    \"time with time zone\",\n\t\t\tResponseUnmarshalledSample: \"01:02:03.123456789-05:00\",\n\t\t\tExpectedGoValue:            time.Date(0, 1, 1, 1, 2, 3, 123456789, time.FixedZone(\"\", -5*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"time with time zone\",\n\t\t\tRawType:                    \"time with time zone\",\n\t\t\tResponseUnmarshalledSample: \"01:02:03.123456789 Europe/Paris\",\n\t\t\tExpectedGoValue:            time.Date(0, 1, 1, 1, 2, 3, 123456789, paris),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"timestamp\",\n\t\t\tRawType:                    \"timestamp\",\n\t\t\tResponseUnmarshalledSample: \"2017-07-10 01:02:03.000\",\n\t\t\tExpectedGoValue:            time.Date(2017, 7, 10, 1, 2, 3, 0, time.Local),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"timestamp with time zone\",\n\t\t\tRawType:                    \"timestamp with time zone\",\n\t\t\tResponseUnmarshalledSample: \"2017-07-10 01:02:03.000 UTC\",\n\t\t\tExpectedGoValue:            time.Date(2017, 7, 10, 1, 2, 3, 0, utc),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"timestamp with time zone\",\n\t\t\tRawType:                    \"timestamp with time zone\",\n\t\t\tResponseUnmarshalledSample: \"2017-07-10 01:02:03.000 +03:00\",\n\t\t\tExpectedGoValue:            time.Date(2017, 7, 10, 1, 2, 3, 0, time.FixedZone(\"\", 3*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"timestamp with time zone\",\n\t\t\tRawType:                    \"timestamp with time zone\",\n\t\t\tResponseUnmarshalledSample: \"2017-07-10 01:02:03.000+03:00\",\n\t\t\tExpectedGoValue:            time.Date(2017, 7, 10, 1, 2, 3, 0, time.FixedZone(\"\", 3*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"timestamp with time zone\",\n\t\t\tRawType:                    \"timestamp with time zone\",\n\t\t\tResponseUnmarshalledSample: \"2017-07-10 01:02:03.000 -04:00\",\n\t\t\tExpectedGoValue:            time.Date(2017, 7, 10, 1, 2, 3, 0, time.FixedZone(\"\", -4*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"timestamp with time zone\",\n\t\t\tRawType:                    \"timestamp with time zone\",\n\t\t\tResponseUnmarshalledSample: \"2017-07-10 01:02:03.000-04:00\",\n\t\t\tExpectedGoValue:            time.Date(2017, 7, 10, 1, 2, 3, 0, time.FixedZone(\"\", -4*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"timestamp\",\n\t\t\tRawType:                    \"timestamp\",\n\t\t\tResponseUnmarshalledSample: \"2017-07-10 01:02:03.123456789\",\n\t\t\tExpectedGoValue:            time.Date(2017, 7, 10, 1, 2, 3, 123456789, time.Local),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"timestamp with time zone\",\n\t\t\tRawType:                    \"timestamp with time zone\",\n\t\t\tResponseUnmarshalledSample: \"2017-07-10 01:02:03.123456789 UTC\",\n\t\t\tExpectedGoValue:            time.Date(2017, 7, 10, 1, 2, 3, 123456789, utc),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"timestamp with time zone\",\n\t\t\tRawType:                    \"timestamp with time zone\",\n\t\t\tResponseUnmarshalledSample: \"2017-07-10 01:02:03.123456789 +03:00\",\n\t\t\tExpectedGoValue:            time.Date(2017, 7, 10, 1, 2, 3, 123456789, time.FixedZone(\"\", 3*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"timestamp with time zone\",\n\t\t\tRawType:                    \"timestamp with time zone\",\n\t\t\tResponseUnmarshalledSample: \"2017-07-10 01:02:03.123456789+03:00\",\n\t\t\tExpectedGoValue:            time.Date(2017, 7, 10, 1, 2, 3, 123456789, time.FixedZone(\"\", 3*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"timestamp with time zone\",\n\t\t\tRawType:                    \"timestamp with time zone\",\n\t\t\tResponseUnmarshalledSample: \"2017-07-10 01:02:03.123456789 -04:00\",\n\t\t\tExpectedGoValue:            time.Date(2017, 7, 10, 1, 2, 3, 123456789, time.FixedZone(\"\", -4*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"timestamp with time zone\",\n\t\t\tRawType:                    \"timestamp with time zone\",\n\t\t\tResponseUnmarshalledSample: \"2017-07-10 01:02:03.123456789-04:00\",\n\t\t\tExpectedGoValue:            time.Date(2017, 7, 10, 1, 2, 3, 123456789, time.FixedZone(\"\", -4*3600)),\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"timestamp with time zone\",\n\t\t\tRawType:                    \"timestamp with time zone\",\n\t\t\tResponseUnmarshalledSample: \"2017-07-10 01:02:03.123456789 Europe/Paris\",\n\t\t\tExpectedGoValue:            time.Date(2017, 7, 10, 1, 2, 3, 123456789, paris),\n\t\t},\n\t\t{\n\t\t\tDataType: \"map(varchar,varchar)\",\n\t\t\tRawType:  \"map\",\n\t\t\tArguments: []typeArgument{\n\t\t\t\t{\n\t\t\t\t\tKind: \"NAMED_TYPE\",\n\t\t\t\t\tnamedTypeSignature: namedTypeSignature{\n\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\tRawType: \"varchar\",\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\tKind: \"NAMED_TYPE\",\n\t\t\t\t\tnamedTypeSignature: namedTypeSignature{\n\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\tRawType: \"varchar\",\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\tResponseUnmarshalledSample: nil,\n\t\t\tExpectedGoValue:            nil,\n\t\t},\n\t\t{\n\t\t\t// arrays return data as-is for slice scanners\n\t\t\tDataType: \"array(varchar)\",\n\t\t\tRawType:  \"array\",\n\t\t\tArguments: []typeArgument{\n\t\t\t\t{\n\t\t\t\t\tKind: \"NAMED_TYPE\",\n\t\t\t\t\tnamedTypeSignature: namedTypeSignature{\n\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\tRawType: \"varchar\",\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\tResponseUnmarshalledSample: nil,\n\t\t\tExpectedGoValue:            nil,\n\t\t},\n\t\t{\n\t\t\t// rows return data as-is for slice scanners\n\t\t\tDataType: \"row(int, varchar(1), timestamp, array(varchar(1)))\",\n\t\t\tRawType:  \"row\",\n\t\t\tArguments: []typeArgument{\n\t\t\t\t{\n\t\t\t\t\tKind: \"NAMED_TYPE\",\n\t\t\t\t\tnamedTypeSignature: namedTypeSignature{\n\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\tRawType: \"integer\",\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\tKind: \"NAMED_TYPE\",\n\t\t\t\t\tnamedTypeSignature: namedTypeSignature{\n\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\tRawType: \"varchar\",\n\t\t\t\t\t\t\tArguments: []typeArgument{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tKind: \"LONG\",\n\t\t\t\t\t\t\t\t\tlong: 1,\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\tKind: \"NAMED_TYPE\",\n\t\t\t\t\tnamedTypeSignature: namedTypeSignature{\n\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\tRawType: \"timestamp\",\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\tKind: \"NAMED_TYPE\",\n\t\t\t\t\tnamedTypeSignature: namedTypeSignature{\n\t\t\t\t\t\tTypeSignature: typeSignature{\n\t\t\t\t\t\t\tRawType: \"array\",\n\t\t\t\t\t\t\tArguments: []typeArgument{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tKind: \"TYPE\",\n\t\t\t\t\t\t\t\t\ttypeSignature: typeSignature{\n\t\t\t\t\t\t\t\t\t\tRawType: \"varchar\",\n\t\t\t\t\t\t\t\t\t\tArguments: []typeArgument{\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tKind: \"LONG\",\n\t\t\t\t\t\t\t\t\t\t\t\tlong: 1,\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\tResponseUnmarshalledSample: []interface{}{\n\t\t\t\tjson.Number(\"1\"),\n\t\t\t\t\"a\",\n\t\t\t\t\"2017-07-10 01:02:03.000 UTC\",\n\t\t\t\t[]interface{}{\"b\"},\n\t\t\t},\n\t\t\tExpectedGoValue: []interface{}{\n\t\t\t\tjson.Number(\"1\"),\n\t\t\t\t\"a\",\n\t\t\t\t\"2017-07-10 01:02:03.000 UTC\",\n\t\t\t\t[]interface{}{\"b\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDataType:                   \"Geometry\",\n\t\t\tRawType:                    \"Geometry\",\n\t\t\tResponseUnmarshalledSample: \"Point (0 0)\",\n\t\t\tExpectedGoValue:            \"Point (0 0)\",\n\t\t},\n\n\t\t{\n\t\t\tDataType:                   \"SphericalGeography\",\n\t\t\tRawType:                    \"SphericalGeography\",\n\t\t\tResponseUnmarshalledSample: \"Point (0 0)\",\n\t\t\tExpectedGoValue:            \"Point (0 0)\",\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tconverter, err := newTypeConverter(tc.DataType, typeSignature{RawType: tc.RawType, Arguments: tc.Arguments})\n\t\tassert.NoError(t, err)\n\n\t\tt.Run(tc.DataType+\":nil\", func(t *testing.T) {\n\t\t\t_, err := converter.ConvertValue(nil)\n\t\t\tassert.NoError(t, err)\n\t\t})\n\n\t\tt.Run(tc.DataType+\":bogus\", func(t *testing.T) {\n\t\t\t_, err := converter.ConvertValue(struct{}{})\n\t\t\tassert.Error(t, err, \"bogus data scanned with no error\")\n\t\t})\n\n\t\tt.Run(tc.DataType+\":sample\", func(t *testing.T) {\n\t\t\tv, err := converter.ConvertValue(tc.ResponseUnmarshalledSample)\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.Equal(t,\n\t\t\t\tv, tc.ExpectedGoValue,\n\t\t\t\t\"unexpected data from sample:\\nhave %+v\\nwant %+v\", v, tc.ExpectedGoValue)\n\t\t})\n\t}\n}\n\nfunc TestSliceTypeConversion(t *testing.T) {\n\ttestcases := []struct {\n\t\tGoType                          string\n\t\tScanner                         sql.Scanner\n\t\tTrinoResponseUnmarshalledSample interface{}\n\t\tTestScanner                     func(t *testing.T, s sql.Scanner, isValid bool)\n\t}{\n\t\t{\n\t\t\tGoType:                          \"[]bool\",\n\t\t\tScanner:                         &NullSliceBool{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{true},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSliceBool)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tGoType:                          \"[]string\",\n\t\t\tScanner:                         &NullSliceString{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{\"hello\"},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSliceString)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tGoType:                          \"[]int64\",\n\t\t\tScanner:                         &NullSliceInt64{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{json.Number(\"1\")},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSliceInt64)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\n\t\t{\n\t\t\tGoType:                          \"[]float64\",\n\t\t\tScanner:                         &NullSliceFloat64{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{json.Number(\"1.0\")},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSliceFloat64)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tGoType:                          \"[]time.Time\",\n\t\t\tScanner:                         &NullSliceTime{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{\"2017-07-01\"},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSliceTime)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tGoType:                          \"[]map[string]interface{}\",\n\t\t\tScanner:                         &NullSliceMap{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{map[string]interface{}{\"hello\": \"world\"}},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSliceMap)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.GoType+\":nil\", func(t *testing.T) {\n\t\t\tassert.NoError(t, tc.Scanner.Scan(nil))\n\t\t})\n\n\t\tt.Run(tc.GoType+\":bogus\", func(t *testing.T) {\n\t\t\tassert.Error(t, tc.Scanner.Scan(struct{}{}))\n\t\t\tassert.Error(t, tc.Scanner.Scan([]interface{}{struct{}{}}), \"bogus data scanned with no error\")\n\t\t})\n\n\t\tt.Run(tc.GoType+\":sample\", func(t *testing.T) {\n\t\t\trequire.NoError(t, tc.Scanner.Scan(tc.TrinoResponseUnmarshalledSample))\n\t\t\ttc.TestScanner(t, tc.Scanner, true)\n\t\t\trequire.NoError(t, tc.Scanner.Scan(nil))\n\t\t\ttc.TestScanner(t, tc.Scanner, false)\n\t\t})\n\t}\n}\n\nfunc TestSlice2TypeConversion(t *testing.T) {\n\ttestcases := []struct {\n\t\tGoType                          string\n\t\tScanner                         sql.Scanner\n\t\tTrinoResponseUnmarshalledSample interface{}\n\t\tTestScanner                     func(t *testing.T, s sql.Scanner, isValid bool)\n\t}{\n\t\t{\n\t\t\tGoType:                          \"[][]bool\",\n\t\t\tScanner:                         &NullSlice2Bool{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{[]interface{}{true}},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSlice2Bool)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tGoType:                          \"[][]string\",\n\t\t\tScanner:                         &NullSlice2String{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{[]interface{}{\"hello\"}},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSlice2String)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tGoType:                          \"[][]int64\",\n\t\t\tScanner:                         &NullSlice2Int64{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{[]interface{}{json.Number(\"1\")}},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSlice2Int64)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tGoType:                          \"[][]float64\",\n\t\t\tScanner:                         &NullSlice2Float64{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{[]interface{}{json.Number(\"1.0\")}},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSlice2Float64)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tGoType:                          \"[][]time.Time\",\n\t\t\tScanner:                         &NullSlice2Time{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{[]interface{}{\"2017-07-01\"}},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSlice2Time)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tGoType:                          \"[][]map[string]interface{}\",\n\t\t\tScanner:                         &NullSlice2Map{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{[]interface{}{map[string]interface{}{\"hello\": \"world\"}}},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSlice2Map)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.GoType+\":nil\", func(t *testing.T) {\n\t\t\tassert.NoError(t, tc.Scanner.Scan(nil))\n\t\t\tassert.NoError(t, tc.Scanner.Scan([]interface{}{nil}))\n\t\t})\n\n\t\tt.Run(tc.GoType+\":bogus\", func(t *testing.T) {\n\t\t\tassert.Error(t, tc.Scanner.Scan(struct{}{}), \"bogus data scanned with no error\")\n\t\t\tassert.Error(t, tc.Scanner.Scan([]interface{}{struct{}{}}), \"bogus data scanned with no error\")\n\t\t\tassert.Error(t, tc.Scanner.Scan([]interface{}{[]interface{}{struct{}{}}}), \"bogus data scanned with no error\")\n\t\t})\n\n\t\tt.Run(tc.GoType+\":sample\", func(t *testing.T) {\n\t\t\trequire.NoError(t, tc.Scanner.Scan(tc.TrinoResponseUnmarshalledSample))\n\t\t\ttc.TestScanner(t, tc.Scanner, true)\n\t\t\trequire.NoError(t, tc.Scanner.Scan(nil))\n\t\t\ttc.TestScanner(t, tc.Scanner, false)\n\t\t})\n\t}\n}\n\nfunc TestSlice3TypeConversion(t *testing.T) {\n\ttestcases := []struct {\n\t\tGoType                          string\n\t\tScanner                         sql.Scanner\n\t\tTrinoResponseUnmarshalledSample interface{}\n\t\tTestScanner                     func(t *testing.T, s sql.Scanner, isValid bool)\n\t}{\n\t\t{\n\t\t\tGoType:                          \"[][][]bool\",\n\t\t\tScanner:                         &NullSlice3Bool{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{[]interface{}{[]interface{}{true}}},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSlice3Bool)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tGoType:                          \"[][][]string\",\n\t\t\tScanner:                         &NullSlice3String{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{[]interface{}{[]interface{}{\"hello\"}}},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSlice3String)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tGoType:                          \"[][][]int64\",\n\t\t\tScanner:                         &NullSlice3Int64{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{[]interface{}{[]interface{}{json.Number(\"1\")}}},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSlice3Int64)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tGoType:                          \"[][][]float64\",\n\t\t\tScanner:                         &NullSlice3Float64{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{[]interface{}{[]interface{}{json.Number(\"1.0\")}}},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSlice3Float64)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tGoType:                          \"[][][]time.Time\",\n\t\t\tScanner:                         &NullSlice3Time{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{[]interface{}{[]interface{}{\"2017-07-01\"}}},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSlice3Time)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tGoType:                          \"[][][]map[string]interface{}\",\n\t\t\tScanner:                         &NullSlice3Map{},\n\t\t\tTrinoResponseUnmarshalledSample: []interface{}{[]interface{}{[]interface{}{map[string]interface{}{\"hello\": \"world\"}}}},\n\t\t\tTestScanner: func(t *testing.T, s sql.Scanner, isValid bool) {\n\t\t\t\tv, _ := s.(*NullSlice3Map)\n\t\t\t\tassert.Equal(t, isValid, v.Valid, \"scanner failed\")\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.GoType+\":nil\", func(t *testing.T) {\n\t\t\tassert.NoError(t, tc.Scanner.Scan(nil))\n\t\t\tassert.NoError(t, tc.Scanner.Scan([]interface{}{[]interface{}{nil}}))\n\t\t})\n\n\t\tt.Run(tc.GoType+\":bogus\", func(t *testing.T) {\n\t\t\tassert.Error(t, tc.Scanner.Scan(struct{}{}), \"bogus data scanned with no error\")\n\t\t\tassert.Error(t, tc.Scanner.Scan([]interface{}{[]interface{}{struct{}{}}}), \"bogus data scanned with no error\")\n\t\t\tassert.Error(t, tc.Scanner.Scan([]interface{}{[]interface{}{[]interface{}{struct{}{}}}}), \"bogus data scanned with no error\")\n\t\t})\n\n\t\tt.Run(tc.GoType+\":sample\", func(t *testing.T) {\n\t\t\trequire.NoError(t, tc.Scanner.Scan(tc.TrinoResponseUnmarshalledSample))\n\t\t\ttc.TestScanner(t, tc.Scanner, true)\n\t\t\trequire.NoError(t, tc.Scanner.Scan(nil))\n\t\t\ttc.TestScanner(t, tc.Scanner, false)\n\t\t})\n\t}\n}\n\nfunc BenchmarkQuery(b *testing.B) {\n\tc := &Config{\n\t\tServerURI:         *integrationServerFlag,\n\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(b, err)\n\n\tdb, err := sql.Open(\"trino\", dsn)\n\trequire.NoError(b, err)\n\n\tb.Cleanup(func() {\n\t\tassert.NoError(b, db.Close())\n\t})\n\n\tq := `SELECT * FROM tpch.sf1.orders LIMIT 10000000`\n\tfor n := 0; n < b.N; n++ {\n\t\trows, err := db.Query(q)\n\t\trequire.NoError(b, err)\n\t\tfor rows.Next() {\n\t\t}\n\t\trows.Close()\n\t}\n}\n\n// BenchmarkSpoolingProtocolSpooledSegmentlJsonZstdDecoderQuery benchmarks the performance of querying a large dataset\n// from Trino with JSON encoding and Zstd compression, testing the spooling mechanism. The query retrieves a result set\n// of 10 million rows, exceeding the default inline row limit of 1000 (defined by `protocol.spooling.inlining.max-rows`),\n// triggering the spooling mechanism to handle the large data efficiently.\n//\n// **Session properties & headers:**\n// - **`encoding: json+zstd`**: Specifies JSON encoding with Zstd compression for the query result.\n// - **`protocol.spooling.inlining.max-rows`**: Default is 1000, determining when spooling is triggered to manage large result sets.\nfunc BenchmarkSpoolingProtocolSpooledSegmentlJsonZstdDecoderQuery(b *testing.B) {\n\tc := &Config{\n\t\tServerURI:         *integrationServerFlag,\n\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(b, err)\n\n\tdb, err := sql.Open(\"trino\", dsn)\n\trequire.NoError(b, err)\n\n\tb.Cleanup(func() {\n\t\tassert.NoError(b, db.Close())\n\t})\n\n\tq := `SELECT * FROM tpch.sf1.orders LIMIT 10000000`\n\tfor n := 0; n < b.N; n++ {\n\t\trows, err := db.Query(q, sql.Named(trinoEncoding, \"json+zstd\"))\n\t\trequire.NoError(b, err)\n\t\tfor rows.Next() {\n\t\t}\n\t\trows.Close()\n\t}\n}\n\n// BenchmarkSpoolingProtocolSpooledSegmentJsonLz4DecoderQuery benchmarks the performance of querying a large dataset\n// from Trino with JSON encoding and LZ4 compression, testing the spooling mechanism. The query retrieves a result set\n// of 10 million rows, exceeding the default inline row limit of 1000 (defined by `protocol.spooling.inlining.max-rows`),\n// triggering the spooling mechanism to handle the large data efficiently.\n//\n// **Session properties & headers:**\n// - **`encoding: json+lz4`**: Specifies JSON encoding with LZ4 compression for the query result.\n// - **`protocol.spooling.inlining.max-rows`**: Default is 1000, determining when spooling is triggered to manage large result sets.\nfunc BenchmarkSpoolingProtocolSpooledSegmentJsonLz4DecoderQuery(b *testing.B) {\n\tc := &Config{\n\t\tServerURI:         *integrationServerFlag,\n\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(b, err)\n\n\tdb, err := sql.Open(\"trino\", dsn)\n\trequire.NoError(b, err)\n\n\tb.Cleanup(func() {\n\t\tassert.NoError(b, db.Close())\n\t})\n\n\tq := `SELECT * FROM tpch.sf1.orders LIMIT 10000000`\n\tfor n := 0; n < b.N; n++ {\n\t\trows, err := db.Query(q, sql.Named(trinoEncoding, \"json+lz4\"))\n\t\trequire.NoError(b, err)\n\t\tfor rows.Next() {\n\t\t}\n\t\trows.Close()\n\t}\n}\n\n// BenchmarkSpoolingProtocolSpooledSegmentJsonDecoderQuery benchmarks the performance of querying a large dataset\n// from Trino with JSON encoding (without compression), testing the spooling mechanism. The query retrieves a result set\n// of 10 million rows, exceeding the default inline row limit of 1000 (defined by `protocol.spooling.inlining.max-rows`),\n// triggering the spooling mechanism to handle the large data efficiently.\n//\n// **Session properties & headers:**\n// - **`encoding: json`**: Specifies JSON encoding without compression for the query result.\n// - **`protocol.spooling.inlining.max-rows`**: Default is 1000, determining when spooling is triggered to manage large result sets\nfunc BenchmarkSpoolingProtocolSpooledSegmentJsonDecoderQuery(b *testing.B) {\n\tc := &Config{\n\t\tServerURI:         *integrationServerFlag,\n\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(b, err)\n\n\tdb, err := sql.Open(\"trino\", dsn)\n\trequire.NoError(b, err)\n\n\tb.Cleanup(func() {\n\t\tassert.NoError(b, db.Close())\n\t})\n\n\tq := `SELECT * FROM tpch.sf1.orders LIMIT 10000000`\n\tfor n := 0; n < b.N; n++ {\n\t\trows, err := db.Query(q, sql.Named(trinoEncoding, \"json\"))\n\t\trequire.NoError(b, err)\n\t\tfor rows.Next() {\n\t\t}\n\t\trows.Close()\n\t}\n}\n\nfunc TestExec(t *testing.T) {\n\tif testing.Short() {\n\t\tt.Skip(\"Skipping test in short mode.\")\n\t}\n\tc := &Config{\n\t\tServerURI:         *integrationServerFlag,\n\t\tSessionProperties: map[string]string{\"query_priority\": \"1\"},\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\tdb, err := sql.Open(\"trino\", dsn)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, db.Close())\n\t})\n\n\t_, err = db.Exec(\"CREATE TABLE memory.default.test (id INTEGER, name VARCHAR, optional VARCHAR)\")\n\trequire.NoError(t, err, \"Failed executing CREATE TABLE query\")\n\n\tresult, err := db.Exec(\"INSERT INTO memory.default.test (id, name, optional) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)\",\n\t\t123, \"abc\", nil,\n\t\t456, \"def\", \"present\",\n\t\t789, \"ghi\", nil)\n\trequire.NoError(t, err, \"Failed executing INSERT query\")\n\t_, err = result.LastInsertId()\n\tassert.Error(t, err, \"trino: operation not supported\")\n\tnumRows, err := result.RowsAffected()\n\trequire.NoError(t, err, \"Failed checking rows affected\")\n\tassert.Equal(t, numRows, int64(3))\n\n\trows, err := db.Query(\"SELECT * FROM memory.default.test\")\n\trequire.NoError(t, err, \"Failed executing DELETE query\")\n\n\texpectedIds := []int{123, 456, 789}\n\texpectedNames := []string{\"abc\", \"def\", \"ghi\"}\n\texpectedOptionals := []sql.NullString{\n\t\tsql.NullString{Valid: false},\n\t\tsql.NullString{String: \"present\", Valid: true},\n\t\tsql.NullString{Valid: false},\n\t}\n\tactualIds := []int{}\n\tactualNames := []string{}\n\tactualOptionals := []sql.NullString{}\n\tfor rows.Next() {\n\t\tvar id int\n\t\tvar name string\n\t\tvar optional sql.NullString\n\t\trequire.NoError(t, rows.Scan(&id, &name, &optional), \"Failed scanning query result\")\n\t\tactualIds = append(actualIds, id)\n\t\tactualNames = append(actualNames, name)\n\t\tactualOptionals = append(actualOptionals, optional)\n\n\t}\n\tassert.Equal(t, expectedIds, actualIds)\n\tassert.Equal(t, expectedNames, actualNames)\n\tassert.Equal(t, expectedOptionals, actualOptionals)\n\n\t_, err = db.Exec(\"DROP TABLE memory.default.test\")\n\trequire.NoError(t, err, \"Failed executing DROP TABLE query\")\n}\n\nfunc TestForwardAuthorizationHeaderConfig(t *testing.T) {\n\tc := &Config{\n\t\tServerURI:                  \"https://foobar@localhost:8090\",\n\t\tForwardAuthorizationHeader: true,\n\t}\n\n\tdsn, err := c.FormatDSN()\n\trequire.NoError(t, err)\n\n\twant := \"https://foobar@localhost:8090?forwardAuthorizationHeader=true&source=trino-go-client\"\n\n\tassert.Equal(t, want, dsn)\n}\n\nfunc TestForwardAuthorizationHeader(t *testing.T) {\n\tvar captureAuthHeader string\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t// Capture the Authorization header for later inspection\n\t\tcaptureAuthHeader = r.Header.Get(\"Authorization\")\n\t}))\n\n\tt.Cleanup(ts.Close)\n\n\tdb, err := sql.Open(\"trino\", ts.URL+\"?forwardAuthorizationHeader=true\")\n\trequire.NoError(t, err)\n\n\t_, _ = db.Query(\"SELECT 1\", sql.Named(\"accessToken\", string(\"token\"))) // Ingore response to focus on header capture\n\trequire.Equal(t, \"Bearer token\", captureAuthHeader, \"Authorization header is incorrect\")\n\n\tassert.NoError(t, db.Close())\n}\n\nfunc TestQueryTimeoutDeadline(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\ttime.Sleep(200 * time.Millisecond) // Simulate slow response\n\t\tw.WriteHeader(http.StatusOK)\n\t}))\n\tdefer ts.Close()\n\n\ttestcases := []struct {\n\t\tname          string\n\t\tqueryTimeout  string\n\t\texpectedError string\n\t}{\n\t\t{\n\t\t\tname:          \"with timeout\",\n\t\t\tqueryTimeout:  \"100ms\",\n\t\t\texpectedError: \"context deadline exceeded\",\n\t\t},\n\t\t{\n\t\t\tname:          \"without timeout\",\n\t\t\tqueryTimeout:  \"10s\",\n\t\t\texpectedError: \"EOF\", // Default server response\n\t\t},\n\t\t{\n\t\t\tname:          \"bad timeout\",\n\t\t\tqueryTimeout:  \"abc\",\n\t\t\texpectedError: \"trino: invalid timeout\", // Default server response\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tprintln(ts.URL + \"?query_timeout=\" + tc.queryTimeout)\n\t\t\tdb, err := sql.Open(\"trino\", ts.URL+\"?query_timeout=\"+tc.queryTimeout)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer db.Close()\n\n\t\t\t_, err = db.Query(\"SELECT 1\")\n\t\t\tassert.ErrorContains(t, err, tc.expectedError)\n\t\t})\n\t}\n}\n"
  }
]