README
¶
Trino Go client
A Trino client for the Go programming
language. It is a driver for the standard database/sql package: your program
opens a *sql.DB and sends SQL statements to Trino over HTTP or HTTPS, and
the driver turns the results into Go values.
The driver authenticates with HTTP Basic credentials, Kerberos, JSON web
tokens or OAuth2, reads results through the spooling protocol when the server
offers it, runs transactions, and converts every Trino type, including nested
ARRAY, MAP, ROW and VARIANT values, into Go values.
Requirements
- Go 1.27 or newer
- Trino 372 or newer
Installation
go get github.com/trinodb/trino-go-client/trino
Quick start
Import the package for its side effect of registering the trino driver, then
open a database with a DSN:
import (
"database/sql"
_ "github.com/trinodb/trino-go-client/trino"
)
db, err := sql.Open("trino", "http://user@localhost:8080?catalog=tpch&schema=sf1")
if err != nil {
return err
}
defer db.Close()
rows, err := db.QueryContext(ctx, "SELECT name, regionkey FROM nation WHERE nationkey < ?", 5)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var name string
var regionKey int64
if err := rows.Scan(&name, ®ionKey); err != nil {
return err
}
}
return rows.Err()
sql.Open returns an error for a DSN that does not parse, and does not
contact the server. Settings that a DSN cannot carry, such as an
http.Client, go in a trino.Config passed to
trino.NewConnector and sql.OpenDB.
Every request the driver sends carries a User-Agent such as
trino-go-client/v0.333.0 os=linux arch=amd64 lang/go=go1.27.1, with the
driver version taken from the module build information (unknown when it is
not available).
Connecting
DSN
The Data Source Name is a URL with a mandatory username, an optional password, an optional catalog and schema path, and optional query string parameters:
http[s]://user[:pass]@host[:port][/catalog[/schema]][?parameters]
The path sets the catalog, or the catalog and schema, with the same syntax as
a JDBC URL:
http://user@localhost:8080/tpch/sf1 is the same as
http://user@localhost:8080?catalog=tpch&schema=sf1. An empty path or a lone
/ sets neither, and one trailing slash is ignored. A path with more than two
segments, or with an empty catalog or schema such as //sf1, is an error. Set
each of the catalog and schema either in the path or with the catalog and
schema parameters, not both: /tpch?schema=sf1 is valid, but
/tpch?catalog=tpch is an error. The path is not a prefix for a server behind
a reverse proxy; requests always go to /v1/statement on the host.
A password enables HTTP Basic authentication
and requires HTTPS. The parameters are case-sensitive. Values
with characters that have a meaning in a URL, such as /, : or ;, must be
URL-encoded, which
Config.FormatDSN
does for you:
dsn, err := (&trino.Config{
ServerURI: "https://user@localhost:8443",
Catalog: "tpch",
SessionProperties: map[string]string{"query_max_run_time": "10m"},
}).FormatDSN()
Examples:
http://user@localhost:8080?source=hello&catalog=default&schema=foobar
https://user:secret@localhost:8443?session_properties=query_max_run_time:10m;query_priority:2
http://user@localhost:8080/tpch/sf1?roles=catalog1:role1;catalog2:role2
Config and Connector
Every DSN parameter has a field on
trino.Config,
and a few settings exist only there, because a string cannot carry them: an
http.Client, a logged-in Kerberos client, and the callbacks of external
authentication. Pass the Config to trino.NewConnector and open the
database with
sql.OpenDB:
connector, err := trino.NewConnector(&trino.Config{
ServerURI: "https://user@localhost:8443",
Catalog: "default",
HTTPClient: &http.Client{Timeout: time.Minute},
})
if err != nil {
return err
}
db := sql.OpenDB(connector)
NewConnector validates the Config and copies it, so later changes to it
have no effect. Config.ServerURI must not have a path; set Config.Catalog
and Config.Schema instead.
HTTPClient is used for every request. Its transport decides how to reach and
verify the coordinator, so it cannot be combined with custom_client, the
certificate parameters, SSLVerification, httpProxy or socksProxy;
Config.TLSConfig builds the tls.Config those settings would produce, for
the client's own transport. As with the default client, the driver does not
follow redirects with HTTPClient, except for spooled segment
downloads. Config.FormatDSN returns an error when a
field that a DSN cannot carry is set.
Parameters
| Parameter | Config field |
Default | Purpose |
|---|---|---|---|
source |
Source |
empty | Name of the application, for query tracking |
catalog |
Catalog |
empty | Default catalog |
schema |
Schema |
empty | Default schema |
session_properties |
SessionProperties |
empty | Session properties |
extra_credentials |
ExtraCredentials |
empty | Credentials for connectors |
roles |
Roles |
empty | Catalog roles |
clientTags |
ClientTags |
empty | Tags for resource groups |
resourceEstimates |
ResourceEstimates |
empty | Expected resource usage, for resource groups |
trace_token |
TraceToken |
empty | Correlation token for the server logs |
client_info |
ClientInfo |
empty | Free-form description of the client |
language |
Language |
server default | Locale for locale-sensitive functions |
timezone |
TimeZone |
local zone | Session time zone |
query_timeout |
QueryTimeout |
10h | Timeout for queries without a context deadline |
request_retry_timeout |
RequestRetryTimeout |
2m | How long one HTTP request is retried |
request_retry_max_attempts |
RequestRetryMaxAttempts |
20 | How many times one HTTP request is sent |
heartbeat_interval |
HeartbeatInterval |
30s | Spooling heartbeat interval |
explicitPrepare |
DisableExplicitPrepare |
true |
Prepared statements through headers or EXECUTE IMMEDIATE |
accessToken |
AccessToken |
empty | JWT sent as a bearer token |
forwardAuthorizationHeader |
ForwardAuthorizationHeader |
false |
Allow a per-query accessToken argument |
externalAuthentication |
ExternalAuthentication |
false |
Log in through the browser |
externalAuthenticationTimeout |
ExternalAuthenticationTimeout |
2m | How long to wait for the login |
oauth2ClientId, oauth2ClientSecret |
OAuth2ClientID, OAuth2ClientSecret |
empty | OAuth2 client credentials |
oauth2TokenEndpoint |
OAuth2TokenEndpoint |
advertised by the server | The only token endpoint the client secret is sent to |
oauth2Scope |
OAuth2Scope |
advertised by the server | Scope requested with the client credentials |
KerberosEnabled |
KerberosEnabled |
false |
Kerberos authentication |
KerberosKeytabPath |
KerberosKeytabPath |
empty | Keytab to log in with |
KerberosCredentialCachePath |
KerberosCredentialCachePath |
KRB5CCNAME |
Credential cache to reuse a ticket from |
KerberosPrincipal |
KerberosPrincipal |
empty | Principal to authenticate as |
KerberosRealm |
KerberosRealm |
empty | Realm of the principal |
KerberosConfigPath |
KerberosConfigPath |
empty | krb5 configuration file |
KerberosRemoteServiceName |
KerberosRemoteServiceName |
trino |
Service name of the coordinator |
KerberosServicePrincipalPattern |
KerberosServicePrincipalPattern |
${SERVICE}@${HOST} |
Service principal of the coordinator |
KerberosUseCanonicalHostname |
KerberosDisableCanonicalHostname |
true |
Resolve the coordinator host before building the principal |
SSLCertPath, SSLCert |
SSLCertPath, SSLCert |
system trust store | Additional CA certificate |
SSLClientCertPath, SSLClientCert, SSLClientKeyPath, SSLClientKey |
same names | empty | Client certificate for mutual TLS |
SSLVerification |
SSLVerification |
FULL |
How the server certificate is verified |
httpProxy, socksProxy |
HTTPProxy, SOCKSProxy |
environment | Proxy for every request |
custom_client |
CustomClientName |
empty | A registered http.Client |
source
Type: string
Valid values: string describing the source of the connection to Trino
Default: empty
The source parameter is sent as the X-Trino-Source header and names the
application in the Trino web UI, the query history and event listeners, so
administrators can trace a query back to the program that ran it.
catalog
Type: string
Valid values: the name of a catalog configured in the Trino server
Default: empty
The catalog parameter defines the Trino catalog where schemas exist to
organize tables. It can also be set as the first segment of the DSN path.
schema
Type: string
Valid values: the name of an existing schema in the catalog
Default: empty
The schema parameter defines the Trino schema where tables exist. This is
also known as namespace in some environments. It can also be set as the
second segment of the DSN path, after the catalog.
session_properties
Type: string
Valid values: semicolon-separated list of key:value session properties
Default: empty
The session_properties parameter must contain valid parameters accepted by
the Trino server. Run SHOW SESSION in Trino to get the current list.
Property names, like extra_credentials and roles keys, must be non-empty
printable ASCII without spaces, = or ,; values are URL-encoded, so they
may contain = and ,. SET SESSION changes a property for the rest of the
session, see Session state and the connection
pool.
extra_credentials
Type: string
Valid values: semicolon-separated list of key:value pairs
Default: empty
The extra_credentials parameter passes credentials to connectors that
authenticate to the underlying system with the user's identity, as described
for each connector in the Trino documentation. Each pair is sent as an
X-Trino-Extra-Credential header with the statement request only, not with
the requests that fetch result pages. Keys follow the same rules as session
property names.
roles
Type: string
Valid values: semicolon-separated list of catalog:role pairs
Default: empty
The roles parameter selects a role to assume in each listed catalog for the
whole session. A role selected with SET ROLE lasts until database/sql
resets the connection for its next caller, and no roles are sent while a SET SESSION AUTHORIZATION switch is active. Roles can also be set for one query
with the X-Trino-Role named argument.
c := &trino.Config{
ServerURI: "https://foobar@localhost:8443",
Roles: map[string]string{"catalog1": "role1", "catalog2": "role2"},
}
dsn, err := c.FormatDSN()
clientTags
Type: string
Valid values: comma-separated list of tags (e.g. tag1,tag2)
Default: empty
The clientTags parameter is sent as the X-Trino-Client-Tags header. Resource
group selectors can match on the tags, so they route queries to resource groups
and help with query tracking. A tag cannot contain ,. The X-Trino-Client-Tags
named argument overrides the tags for one query.
config := &trino.Config{
ServerURI: "http://foobar@localhost:8080",
ClientTags: []string{"tag1", "tag2", "tag3"},
}
dsn, err := config.FormatDSN()
resourceEstimates
Type: string
Valid values: semicolon-separated list of resource:estimate pairs
Default: empty
The resourceEstimates parameter tells the coordinator how much each query is
expected to use, and is sent as one X-Trino-Resource-Estimate header per
estimate with every statement. Resource group selectors can match on these
estimates to route queries, for example to keep long or memory-hungry queries
out of an interactive group. The server accepts these resources, ignoring case:
EXECUTION_TIMEandCPU_TIME, a duration such as90s,10mor1.5h(unitsns,us,ms,s,m,h,d)PEAK_MEMORY, a data size such as512MBor1.5GB(unitsB,kB,MB,GB,TB,PB, all powers of 1024)
The values are sent as given. The server rejects a query with an unknown
resource or a value it cannot parse, so a Go time.Duration must not be passed
through String(), which produces 1h30m0s. Use trino.FormatDuration and
trino.FormatDataSize instead, which produce values the server accepts, such
as 1.50h and 1.50GB. They use the largest unit that represents the value
exactly with two decimal places, so they never round: time.Hour + time.Second
becomes 3601.00s. The driver rejects names that are empty, contain = or
,, or contain characters other than printable ASCII. The
X-Trino-Resource-Estimate named argument adds to or
replaces the estimates for one query.
config := &trino.Config{
ServerURI: "http://foobar@localhost:8080",
ResourceEstimates: map[string]string{
"EXECUTION_TIME": "10m",
"CPU_TIME": trino.FormatDuration(90 * time.Minute), // 1.50h
"PEAK_MEMORY": trino.FormatDataSize(1 << 30), // 1GB
},
}
dsn, err := config.FormatDSN()
trace_token
Type: string
Valid values: any string
Default: empty
The trace_token parameter is sent as the X-Trino-Trace-Token header and is
recorded by the coordinator, so queries made through the connection can be
correlated with the server logs and event listeners.
client_info
Type: string
Valid values: any string
Default: empty
The client_info parameter is sent as the X-Trino-Client-Info header. It is
free-form metadata about the client, shown in the Trino web UI and passed to
event listeners.
language
Type: string
Valid values: a language tag, e.g. en-US
Default: empty (the server default)
The language parameter is sent as the X-Trino-Language header and selects
the locale used by locale-sensitive functions.
timezone
Type: string
Valid values: an IANA time zone name (e.g. Europe/Warsaw), UTC, or an offset
like +05:30
Default: the local time zone of the client
The timezone parameter is sent as the X-Trino-Time-Zone header. The server
uses it to evaluate current_timestamp, now(), date_trunc and every other
function that depends on the session time zone, and the driver uses the same
zone to read DATE, TIME and TIMESTAMP values that do not carry a zone of
their own. Without the parameter the local zone is used, taken from the TZ
environment variable or from /etc/localtime; when neither names a zone, the
current UTC offset is sent instead. Set the parameter explicitly to make the
results independent of where the client runs, for example timezone=UTC.
Running SET TIME ZONE changes the zone for the rest of the session, and the
driver follows it when reading values. The X-Trino-Time-Zone named
argument overrides the zone for one query.
config := &trino.Config{
ServerURI: "http://foobar@localhost:8080",
TimeZone: "Asia/Tokyo",
}
dsn, err := config.FormatDSN()
query_timeout
Type: time.Duration
Valid values: duration string (e.g. 30s, 5m)
Default: unset (10h, trino.DefaultQueryTimeout, for queries whose
context has no deadline)
The query_timeout parameter bounds how long a query may run before the driver
cancels it on the server and returns an error. When set, it applies to every
query, even one whose context has a deadline. When unset, the context deadline
applies, and a context without one gets trino.DefaultQueryTimeout. See
Cancellation and timeouts.
request_retry_timeout
Type: time.Duration
Valid values: duration string
Default: 2m (trino.DefaultRequestRetryTimeout)
The request_retry_timeout parameter sets how long a single HTTP request is
retried on a 429, 502, 503 or 504 response or a network error before
the query fails. Only idempotent requests are retried on network errors after
the connection is established. A Retry-After header on a 429 or 503
response, in seconds or as an HTTP date, sets the wait before the next attempt
instead of the default backoff; the wait never extends past this timeout. The
value must be positive.
request_retry_max_attempts
Type: int
Valid values: positive integer
Default: 20 (trino.DefaultRequestRetryMaxAttempts)
The request_retry_max_attempts parameter sets how many times a single HTTP
request is sent before the query fails. Retrying stops at whichever of
request_retry_timeout and request_retry_max_attempts is reached first; with
the defaults, the timeout is reached first.
heartbeat_interval
Type: time.Duration
Valid values: positive duration string (e.g. 30s, 1m)
Default: unset (30s)
The heartbeat_interval parameter sets how often the driver sends a HEAD
heartbeat to the current nextUri while a spooled query is in progress, so
the coordinator does not abandon a query whose results are consumed slowly.
It applies to the whole connection and is only used with the spooling
protocol.
explicitPrepare
Type: bool
Valid values: true, false
Default: true
The explicitPrepare parameter controls how statements with arguments reach
the server. With true, the driver prepares the statement through the
X-Trino-Prepared-Statement header and runs EXECUTE. With false, it runs
EXECUTE IMMEDIATE, which carries the statement text in the request body
instead of a header, so statements larger than the server's header size limit
still work. The Config field is DisableExplicitPrepare, whose zero value
keeps explicit prepare on.
accessToken
Type: string
Valid values: a JSON web token
Default: empty
The accessToken parameter is sent as a bearer token in the Authorization
header of every request, see JSON web token
authentication.
forwardAuthorizationHeader
Type: bool
Valid values: true, false
Default: false
The forwardAuthorizationHeader parameter allows the accessToken named
argument to set the bearer token of one query, see
Authorization header forwarding.
externalAuthentication
Type: bool
Valid values: true, false
Default: false
The externalAuthentication parameter enables external
authentication. It requires HTTPS.
externalAuthenticationTimeout
Type: time.Duration
Valid values: positive duration string (e.g. 5m)
Default: unset (2m)
The externalAuthenticationTimeout parameter sets how long the driver waits for
the user to log in.
oauth2ClientId / oauth2ClientSecret
Type: string
Valid values: the client ID and secret registered with the identity provider
Default: empty
The oauth2ClientId and oauth2ClientSecret parameters enable OAuth2 client
credentials authentication. They must be set
together and require HTTPS.
oauth2TokenEndpoint
Type: string
Valid values: an https:// URL
Default: empty (the token endpoint the server advertises)
The oauth2TokenEndpoint parameter pins the token endpoint the client
credentials are sent to. A different token endpoint advertised by the server
is rejected. It requires oauth2ClientId.
oauth2Scope
Type: string
Valid values: space separated scopes
Default: empty (the scope the server advertises)
The oauth2Scope parameter sets the scope requested from the token endpoint,
overriding the scope the server advertises. It requires oauth2ClientId.
KerberosEnabled
Type: bool
Valid values: true, false
Default: false
The KerberosEnabled parameter turns on Kerberos
authentication.
KerberosKeytabPath
Type: string
Valid values: a filesystem path
Default: empty (a credential cache is used)
The KerberosKeytabPath parameter names the keytab the driver logs in with,
as KerberosPrincipal in KerberosRealm. It cannot be combined with
KerberosCredentialCachePath.
KerberosCredentialCachePath
Type: string
Valid values: a filesystem path, optionally prefixed with FILE:
Default: empty (KRB5CCNAME, then /tmp/krb5cc_<uid>, when no keytab is given)
A Kerberos credential cache holding a ticket for the user, used instead of a
keytab; it cannot be combined with KerberosKeytabPath. Only file caches can
be read: when KRB5CCNAME names another type, such as KEYRING:, KCM: or
macOS's default API:, run kinit -c FILE:/path and set this parameter. The
cache is read when a connection opens, so connections opened after kinit
refreshes it use the new ticket.
KerberosPrincipal
Type: string
Valid values: a Kerberos principal name, without the realm
Default: empty (the principal of the credential cache)
The KerberosPrincipal parameter names the user to authenticate as. With a
keytab, the driver logs in as this principal. With a credential cache or a
KerberosClient it is optional, and when set must match the principal the
cache or client holds.
KerberosRealm
Type: string
Valid values: a Kerberos realm
Default: empty (the realm of the credential cache)
The KerberosRealm parameter is the realm of KerberosPrincipal. With a
credential cache or a KerberosClient it must match their realm when set.
KerberosConfigPath
Type: string
Valid values: a filesystem path
Default: empty
The KerberosConfigPath parameter names the krb5 configuration file, usually
/etc/krb5.conf, which lists the KDCs and maps host names to realms.
KerberosRemoteServiceName
Type: string
Valid values: a Kerberos service name
Default: trino
The KerberosRemoteServiceName parameter is the service name of the
coordinator, substituted for ${SERVICE} in
KerberosServicePrincipalPattern.
KerberosServicePrincipalPattern
Type: string
Valid values: a service principal with optional ${SERVICE} and ${HOST} placeholders
Default: ${SERVICE}@${HOST}
The service principal of the coordinator. ${SERVICE} is replaced with
KerberosRemoteServiceName and ${HOST} with the lowercased host of the
request URL. A service@host result names the Kerberos principal
service/host, so the default asks for a ticket for trino/<host>; a result
without @, such as HTTP/${HOST}, is used as the principal name. The realm
of the service comes from domain_realm in the krb5 configuration, or is the
user's realm, and cannot be part of the pattern.
KerberosUseCanonicalHostname
Type: bool
Valid values: true, false
Default: true
${HOST} in
KerberosServicePrincipalPattern is the
canonical name of the coordinator host instead of the host in the URL, for a
coordinator reached through a DNS alias or a load balancer. The host is
resolved to an address, following CNAME records, and the first address is
looked up in reverse DNS; without a reverse record the address itself is used.
localhost and loopback addresses are replaced with the canonical name of the
local machine, and requests fail with Fully qualified name of localhost should not resolve to 'localhost' when that resolves to localhost too, as
on many laptops. The name is resolved once per connection and host. Set it to
false, or set Config.KerberosDisableCanonicalHostname, to use the URL host
as is.
Earlier versions of this driver always used the URL host. If you reach the
coordinator through a DNS alias and its service principal is registered for
that alias, set KerberosUseCanonicalHostname=false to keep asking for the
same ticket.
SSLCertPath / SSLCert
Type: string
Valid values: a filesystem path (SSLCertPath) or PEM-encoded certificate content (SSLCert)
Default: empty (the system trust store is used)
An additional CA certificate the driver should trust, as a file path or as
inline PEM content; only one of the pair may be set. Requires HTTPS, and
cannot be combined with custom_client or HTTPClient, whose transports have
their own TLS configuration.
SSLClientCertPath / SSLClientCert and SSLClientKeyPath / SSLClientKey
Type: string
Valid values: a filesystem path (...Path) or PEM-encoded content
Default: empty (no client certificate is presented)
A PEM client certificate and its private key, for TLS client authentication
(mutual TLS). The certificate and the key must each be given as either a path
or inline content, not both, and the certificate and key must be set
together. Requires HTTPS, and cannot be combined with custom_client or
HTTPClient.
db, err := sql.Open("trino", "https://user@localhost:8443"+
"?SSLClientCertPath=/path/to/client-cert.pem"+
"&SSLClientKeyPath=/path/to/client-key.pem")
SSLVerification
Type: string
Valid values: "FULL", "CA", "NONE"
Default: "FULL"
Controls how the driver validates the coordinator's TLS certificate. FULL
validates the certificate chain and the hostname. CA validates the
certificate chain but not the hostname. NONE disables certificate validation
entirely and must only be used for development, since it also allows a network
attacker to intercept the connection. Requires HTTPS, and cannot be combined
with custom_client or HTTPClient.
httpProxy / socksProxy
Type: string
Valid values: host:port
Default: empty (the proxy environment variables apply)
Sends every request through an HTTP proxy (httpProxy) or a SOCKS5 proxy
(socksProxy). Only one of the two may be set. HTTPS requests reach the
coordinator through an HTTP CONNECT tunnel, and the SOCKS5 proxy resolves
the coordinator's host name. Neither can be combined with custom_client or
HTTPClient; see Proxy.
db, err := sql.Open("trino", "http://user@localhost:8080?socksProxy=localhost:1080")
custom_client
Type: string
Valid values: the name of a client previously registered to the driver
Default: empty (defaults to http.DefaultClient)
The custom_client parameter selects an http.Client registered with
trino.RegisterCustomClient for every request, see Custom HTTP
client. Its transport decides how to verify the
coordinator and how to reach it, so the driver rejects SSLCert,
SSLCertPath, the client certificate parameters, SSLVerification,
httpProxy and socksProxy combined with it, instead of ignoring them.
Authentication
HTTP Basic authentication
A password in the DSN, or in Config.ServerURI, enables HTTP Basic
authentication: the driver sets the Authorization header in every request to
Trino. It requires HTTPS; a password over http is an error.
Kerberos authentication
Kerberos authentication is enabled with KerberosEnabled=true and configured
with the Kerberos* parameters, on the DSN or on Config. The
coordinator only offers Kerberos authentication over HTTPS.
The driver logs in with the keytab in KerberosKeytabPath, or reuses a ticket
from a credential cache, such as the one kinit writes. Without a keytab it
reads the cache in KerberosCredentialCachePath, then the one named by the
KRB5CCNAME environment variable, then /tmp/krb5cc_<uid>. The principal and
realm come from the cache; when KerberosPrincipal or KerberosRealm are set,
they must match it.
db, err := sql.Open("trino", "https://user@localhost:8443"+
"?KerberosEnabled=true"+
"&KerberosConfigPath=/etc/krb5.conf"+
"&KerberosCredentialCachePath=/tmp/krb5cc_1000")
A program that already holds a logged-in
gokrb5 client, for
example one that authenticates its own users, can pass it in
Config.KerberosClient with trino.NewConnector. All connections of the
Connector share it and the driver never logs it in, renews or destroys it. It
requires KerberosEnabled and cannot be combined with KerberosKeytabPath or
KerberosCredentialCachePath. When KerberosPrincipal or KerberosRealm are
set, they must match the client's credentials.
connector, err := trino.NewConnector(&trino.Config{
ServerURI: "https://user@localhost:8443",
KerberosEnabled: true,
KerberosClient: kerberosClient,
})
The ticket is requested for the service principal that
KerberosServicePrincipalPattern and
KerberosUseCanonicalHostname produce. See
Kerberos authentication
in the Trino documentation for the server side.
JSON web token authentication
A JWT in the accessToken parameter, or in Config.AccessToken,
is sent as a bearer token in the Authorization header of every request. See
JWT authentication in the
Trino documentation for the server side.
External authentication
With externalAuthentication=true, a coordinator that uses
OAuth2 authentication
logs the user in through the browser. When the coordinator rejects a request,
the driver passes the login URL to Config.RedirectHandler, waits for the
token until externalAuthenticationTimeout (default 2m) passes, and retries
the request with it, so a query running when the token expires continues. The
token is kept in Config.TokenCache and shared by all connections of a
Connector. By default, RedirectHandler is trino.OpenBrowser, and the
cache holds the token in memory.
connector, err := trino.NewConnector(&trino.Config{
ServerURI: "https://trino.example.com:8443",
ExternalAuthentication: true,
RedirectHandler: func(ctx context.Context, redirectURL *url.URL) error {
fmt.Println("Log in at", redirectURL)
return nil
},
})
Implement trino.TokenCache to keep the token between runs of a program.
External authentication requires HTTPS, and cannot be combined with a
password, Kerberos, or forwardAuthorizationHeader. An AccessToken is sent
until a token is cached.
OAuth2 client credentials
A service without a user to log in can authenticate to a coordinator that uses
OAuth2 authentication
with the OAuth2 client credentials grant, by setting
oauth2ClientId and oauth2ClientSecret
to a client registered with the coordinator's identity provider. A request
without a valid token is rejected by the coordinator, and Trino 484 and later
advertise the identity provider's token endpoint and the scope to request in
the WWW-Authenticate challenge of that response. The driver then exchanges
the client ID and secret for an access token at that endpoint, by posting
grant_type=client_credentials with the client ID and secret as form fields
(client_secret_post), and sends the rejected request again with the access
token as a bearer token.
connector, err := trino.NewConnector(&trino.Config{
ServerURI: "https://trino.example.com:8443",
OAuth2ClientID: "trino-client",
OAuth2ClientSecret: secret,
})
The same settings in a DSN:
https://trino.example.com:8443?oauth2ClientId=trino-client&oauth2ClientSecret=...
The token is shared by all connections of a Connector and is sent with
every request until 30 seconds before the expires_in the token endpoint
returned, or 5 minutes when it returned none; the next request then gets a new
token. A token the coordinator rejects is replaced once per request.
The client secret is never sent to the coordinator, and only to an https://
token endpoint, which does not follow redirects. Set
oauth2TokenEndpoint to send it only to an endpoint
you trust: the driver then rejects a different endpoint the coordinator
advertises, so a compromised coordinator cannot collect the secret. It also
lets the driver use the client credentials grant with coordinators older than
Trino 484, which do not advertise an endpoint. Set
oauth2Scope to request another scope than the one the
coordinator advertises. The token endpoint is reached with the same HTTP
client, TLS settings and proxy as the coordinator.
Client credentials authentication requires HTTPS, and cannot be combined
with external authentication, a password, an accessToken, Kerberos, or
forwardAuthorizationHeader.
An identity provider that needs another way to authenticate the client, like
HTTP Basic authentication (client_secret_basic), can be used through an
HTTPClient from
golang.org/x/oauth2,
which adds the token to every request and renews it when it expires:
import "golang.org/x/oauth2/clientcredentials"
credentials := clientcredentials.Config{
ClientID: "trino-client",
ClientSecret: secret,
TokenURL: "https://idp.example.com/oauth2/token",
}
connector, err := trino.NewConnector(&trino.Config{
ServerURI: "https://trino.example.com:8443",
HTTPClient: credentials.Client(context.Background()),
})
Authorization header forwarding
A program that holds a different token for each of its users can send it with
each query: set forwardAuthorizationHeader=true, or
Config.ForwardAuthorizationHeader, and pass the token as the accessToken
named argument. It overrides the AccessToken of the
connection for that query.
rows, err := db.Query("SELECT * FROM foobar", sql.Named("accessToken", userToken))
Using the accessToken named argument without enabling
forwardAuthorizationHeader returns an error, so the token is never sent as
part of the query text, where Trino would persist it in the query history.
Per-query user and session authorization
A query can run as a user other than the one that authenticated to the
coordinator, when system access
control
allows the impersonation. Pass the user as the X-Trino-User named
argument; it is sent as a header and is not part of the
query:
db.Query("SELECT * FROM foobar WHERE id=?", 1, sql.Named("X-Trino-User", "Alice"))
Trino 426 and newer also let a session switch its authorization user with SET SESSION AUTHORIZATION <user>, reverted with RESET SESSION AUTHORIZATION.
Statements after the switch run as the new user until the reset. The switch is
held by the underlying connection, and database/sql resets a connection when
it takes it from the pool, so run the switch and every statement that should
run as the new user on one sql.Conn:
conn, err := db.Conn(ctx)
if err != nil {
return err
}
defer conn.Close()
if _, err := conn.ExecContext(ctx, "SET SESSION AUTHORIZATION bob"); err != nil {
return err
}
// Runs as bob. A db.Query here would run as the DSN user instead.
rows, err := conn.QueryContext(ctx, "SELECT current_user")
While the switch is active, the roles from the roles parameter and any role
selected with SET ROLE are not sent; they apply again after the reset. Trino
rejects both statements inside a transaction.
Per-query settings
database/sql has no way to pass settings for one query, so the driver reads
them from sql.Named arguments. They
are consumed by the driver and never reach the query text, and their position
among the other arguments does not matter. Any argument whose name starts with
X-Trino- is sent as a request header with that name, which overrides the
connection's value of that header for the query. The arguments with a special
meaning are:
| Name | Value | Purpose |
|---|---|---|
X-Trino-User |
string |
Run the query as another user, see Per-query user |
X-Trino-Role |
map[string]string |
Catalog roles for the query, formatted like roles |
X-Trino-Resource-Estimate |
map[string]string |
Estimates added to or replacing the connection's resourceEstimates |
X-Trino-Client-Tags |
string |
Comma-separated tags replacing clientTags |
X-Trino-Time-Zone |
string |
Time zone replacing timezone, also for reading the results |
X-Trino-Progress-Callback |
trino.ProgressUpdater |
Query id and statistics, see Query id and progress |
X-Trino-Progress-Callback-Period |
time.Duration |
How often the callback runs |
warnings |
*trino.Warnings |
Collects the warnings of the query, see Query warnings |
accessToken |
string |
Bearer token for the query, see Authorization header forwarding |
encoding |
string |
Spooled result encodings, see Spooling protocol |
spooling_worker_count |
string |
Parallel segment downloads, a number |
max_out_of_order_segments |
string |
Buffered out-of-order segments, a number |
Query id and progress
database/sql has no way to expose the Trino query id or the statistics the
coordinator reports while a query runs. The driver makes them available through
a callback passed as a pair of named arguments:
X-Trino-Progress-Callback- a value implementing thetrino.ProgressUpdaterinterfaceX-Trino-Progress-Callback-Period- atime.Durationlimiting how often the callback is invoked while the query state does not change
Both must be set together. The callback receives a trino.QueryProgressInfo
with the QueryId and the current query statistics. It is invoked when the
query is submitted, when a result page is received, and once more when the
query finishes.
The callback runs on its own goroutine, one invocation at a time and in the order the updates arrived. The driver waits for it to accept every update rather than dropping any, so a change of the query state is never missed, but a callback slower than the result pages delays fetching the next page. Keep it quick, or hand the update off to another goroutine if it does more than record the statistics.
For a query using the spooling protocol, FailedSegmentAcknowledgments counts
the segments the driver read but could not acknowledge. Those segments stay in
storage until they expire. Acknowledgments can finish after the query does, so
closing the rows waits for them and, if any failed since the last invocation,
invokes the callback once more with the final count.
type queryLogger struct{}
func (queryLogger) Update(info trino.QueryProgressInfo) {
log.Printf("query %s is %s, %.0f%% done", info.QueryId, info.QueryStats.State, info.QueryStats.ProgressPercentage)
}
rows, err := db.Query("SELECT * FROM foobar",
sql.Named("X-Trino-Progress-Callback", queryLogger{}),
sql.Named("X-Trino-Progress-Callback-Period", time.Second),
)
Query warnings
The coordinator can attach warnings to a query, such as deprecation notices,
which database/sql also has no way to expose. Pass a *trino.Warnings as
the warnings named argument; once the rows are consumed, All returns every
distinct warning the coordinator reported, in the order it first reported it.
Without a warnings argument, warnings the coordinator reports are dropped.
var warnings trino.Warnings
rows, err := db.Query("SELECT * FROM foobar", sql.Named("warnings", &warnings))
// ... consume rows ...
for _, w := range warnings.All() {
log.Printf("warning %s: %s", w.Name, w.Message)
}
Sessions and transactions
Session state and the connection pool
Statements that change the session, such as USE, SET PATH, SET SESSION,
SET TIME ZONE, SET ROLE, PREPARE and SET SESSION AUTHORIZATION, change
it on the underlying connection only. When database/sql hands that connection
to the next caller, the driver puts back the user, catalog, schema, session
properties, roles and time zone from the DSN, and drops the path and prepared
statements. To run several statements in one session, use one
sql.Conn:
conn, err := db.Conn(ctx)
if err != nil {
return err
}
defer conn.Close()
if _, err := conn.ExecContext(ctx, "USE tpch.tiny"); err != nil {
return err
}
// Reads tpch.tiny.nation. A db.Query here would use the DSN catalog and schema.
rows, err := conn.QueryContext(ctx, "SELECT * FROM nation")
Transactions
Use db.Begin or db.BeginTx to run several statements in a single Trino
transaction:
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
if _, err := tx.ExecContext(ctx, "DELETE FROM reports WHERE day = DATE '2025-01-01'"); err != nil {
return err
}
if _, err := tx.ExecContext(ctx, "INSERT INTO reports SELECT * FROM staging"); err != nil {
return err
}
return tx.Commit()
sql.TxOptions is honoured. Isolation accepts sql.LevelDefault,
sql.LevelReadUncommitted, sql.LevelReadCommitted, sql.LevelRepeatableRead
and sql.LevelSerializable; any other level is rejected. ReadOnly starts a
READ ONLY transaction.
Commit and Rollback carry the values of the context passed to BeginTx,
such as trace spans, but not its cancellation or deadline, so the rollback
database/sql runs when that context is cancelled still reaches the server.
A statement that fails aborts the whole transaction on the server. Rollback
reports success in that case, while Commit returns an error.
Closing rows before the last page cancels the query on the server, and Trino
aborts the transaction when a query inside it is cancelled. A tx.QueryRow
over a result that spans more than one page, or a rows.Close before the end,
therefore leaves Commit failing with TRANSACTION_ALREADY_ABORTED. Inside a
transaction, read the rows to the end or add a LIMIT to the query.
Transaction control statements must go through the returned *sql.Tx. Sending
them directly, as in db.Exec("START TRANSACTION"), is rejected.
[!NOTE] Transaction support depends on the connector. Connectors that only support writes in autocommit mode, such as
memory, reject writes inside a multi-statement transaction, and the isolation levels a connector accepts vary.
Connection validation and server version
Opening a connection does not contact the server. db.PingContext fetches the
coordinator's /v1/info and fails when the server cannot be reached, answers
with an error, or is still starting up. Since that endpoint does not require
authentication, the ping then sends HEAD /v1/statement with the connection's
credentials and fails when the server rejects them. No query is started.
Servers older than Trino 469 do not support that request, so for them a
successful ping does not prove the credentials are accepted. With external
authentication, that request starts the login flow
when no valid token is cached, so a ping can open the browser.
Before returning a connection to the pool, database/sql asks the driver
whether it can be reused, without contacting the server. A connection whose
Kerberos client failed to authenticate a request is dropped, so the next one
reloads the keytab or credential cache, for example after kinit renewed an
expired ticket; a statement that failed that way before reaching the server
is retried on a new connection.
The same information, including the server version, is available from
trino.Conn.ServerInfo through
sql.Conn.Raw:
conn, err := db.Conn(ctx)
if err != nil {
return err
}
defer conn.Close()
var info trino.ServerInfo
err = conn.Raw(func(driverConn any) error {
info, err = driverConn.(*trino.Conn).ServerInfo(ctx)
return err
})
if err != nil {
return err
}
log.Printf("Trino %s in %s, up for %s", info.NodeVersion, info.Environment, info.Uptime)
Data types
Query arguments
When passing arguments to queries, the driver supports the following Go data types:
- integers
float32andfloat64- passed to Trino asREALandDOUBLEliteralsboolstring[]byte- slices - passed to Trino as an
ARRAY - maps - passed to Trino as a
MAP, built asMAP(ARRAY[keys...], ARRAY[values...]); an empty or nil map is passed asMAP() trino.Numeric- a string representation of a numbertime.Time- passed to Trino as a timestamp with a time zone- the result of
trino.Date(year, month, day)- passed to Trino as a date - the result of
trino.Time(hour, minute, second, nanosecond)- passed to Trino as a time without a time zone - the result of
trino.TimeTz(hour, minute, second, nanosecond, location)- passed to Trino as a time with a time zone - the result of
trino.Timestamp(year, month, day, hour, minute, second, nanosecond)- passed to Trino as a timestamp without a time zone time.Duration- passed to Trino as an interval day to second. A whole number of milliseconds works with every Trino version. A finer duration, like1500 * time.Microsecond, is passed with its precision declared, as inINTERVAL '0.0015' SECOND(1, 4), which needs Trino 484 or later; older servers reject the query instead of truncating the value to milliseconds.trino.Interval- passed to Trino as an interval year to month whenMonthsis set, and otherwise as an interval day to second like atime.Duration. Non-zeroPicosecondsare passed with as many fractional-second digits as they need, up to 12, which also needs Trino 484 or later. SettingMonthstogether withDurationorPicosecondsis an error, and anIntervalthat is notValidis passed asNULL.
It's not yet possible to pass:
bytejson.RawMessageVARIANTvalues; pass the JSON text as a string and convert it withCAST(json_parse(?) AS VARIANT)
To use the unsupported types, pass them as strings and use casts in the query, like so:
SELECT *
FROM table
WHERE col_json = CAST(? AS JSON) OR col_timestamp = CAST(? AS TIMESTAMP)
Response rows
The driver decodes each value straight into the Go value of its column type,
which is what a column scanned into an interface{} holds:
| Trino type | Go type |
|---|---|
NULL of any type |
nil |
BOOLEAN |
bool |
TINYINT, SMALLINT, INTEGER, BIGINT |
int64 |
REAL, DOUBLE |
float64 |
DECIMAL, NUMBER, CHAR, VARCHAR, JSON, UUID, IPADDRESS, INTERVAL YEAR TO MONTH, INTERVAL DAY TO SECOND, Geometry, SphericalGeography, color |
string |
VARBINARY and every type without a textual form, like HyperLogLog |
[]byte |
DATE, TIME, TIMESTAMP, with or without a time zone |
time.Time |
VARIANT |
trino.Variant |
ARRAY |
[]interface{} |
MAP |
map[string]interface{} |
ROW |
trino.Row |
BingTile, KdbTree |
map[string]interface{} |
The elements of an ARRAY, the values of a MAP and the fields of a ROW
are decoded the same way, at any depth, so an ARRAY(VARBINARY) holds
[]byte elements and an ARRAY(TIMESTAMP) holds time.Time elements. The
keys of a MAP are strings, the way the server sends them, like "1" for a
MAP(INTEGER, VARCHAR); trino.NullMap converts them into its key type. A
value that does not match its column type fails the query with an error
naming the row and the column.
The details for some of the types:
- time and timestamps with precision - all time types are returned as
time.Time. All precisions up to nanoseconds (TIMESTAMP(9)orTIME(9)) are supported (since this is the maximum precision Golang'stime.Timesupports). If a query returns columns defined with a greater precision, values are trimmed to 9 decimal digits. UseCASTto reduce the returned precision, or convert the value to a string that then can be parsed manually. DATE,TIMEandTIMESTAMPwithout a time zone - returned astime.Timein the zone of the connection (see thetimezoneparameter), which is the zone the server used to produce them, also inside anARRAY,MAPorROW.MAPkeys of these types are parsed bytrino.NullMap, in the zone of itsLocationfield,time.Localby default.DECIMALandNUMBER(Trino 480+) - returned as string; usesql.NullStringfor nullable columnsIPADDRESS- returned as stringINTERVAL YEAR TO MONTHandINTERVAL DAY TO SECOND- returned as string, like1-2or-3 04:05:06.789, or scanned intotrino.Interval, which holds a year to month interval asMonthsand a day to second interval as atime.Duration. Scanning a day to second interval longer than about 292 years, which atime.Durationcannot hold, returns an error- parametric intervals (Trino 484+) - intervals have a leading-field precision
and up to 12 fractional-second digits, like
INTERVAL SECOND(2, 12)orINTERVAL DAY(3) TO SECOND(9), also inside anARRAY,MAPorROW, and a value has exactly as many fractional digits as its type, none for a precision of 0. Older servers send every day to second interval rounded to milliseconds.trino.Intervalkeeps all 12 digits:Durationholds the value truncated toward zero to nanoseconds, andPicosecondsthe remaining picoseconds, from -999 to 999, with the same sign as the value. A value with more than 12 fractional digits is an error.sql.ColumnType.DatabaseTypeName()returns the full type, likeINTERVAL SECOND(2, 0),INTERVAL DAY(3) TO SECOND(9)orINTERVAL YEAR(5) TO MONTH, andsql.ColumnType.DecimalSize()reports the leading-field precision as the precision and the fractional-seconds precision as the scale. Older servers reportINTERVAL DAY TO SECONDorINTERVAL YEAR TO MONTH, without a precision or scale UUID- returned as stringVARIANT(Trino 481+) - returned astrino.Variant, see VARIANTGeometry,SphericalGeographyandcolor- returned as stringBingTileandKdbTree- returned asmap[string]interface{}, the JSON object the server sent, with numbers asfloat64- any other type, like
HyperLogLog,SetDigest,QDigest, andTDigest- returned as[]byte, decoded from the base64 form the server sends, the same asVARBINARY
For reading nullable columns, use trino.NullTime or similar structs from
the database/sql package, like sql.NullInt64.
Since Trino does not report whether a column can hold NULL,
sql.ColumnType.Nullable reports every column as nullable.
ARRAY, MAP and ROW
To read ARRAY, MAP and ROW values, use the generic scanners, which nest
to any depth:
trino.NullSlice[T]for anARRAY, with its elements inSlicetrino.NullMap[K, V]for aMAP, with its entries inMaptrino.NullRow[T]for aROW, with its fields stored in the structTinRow
Each one has a Valid field that is false for a NULL value, at every
level, so a NULL inner array is told apart from an empty one:
var tags trino.NullSlice[trino.NullSlice[sql.NullString]]
err := db.QueryRow("SELECT ARRAY[ARRAY['a', NULL], NULL]").Scan(&tags)
// tags.Slice[0].Slice == []sql.NullString{{String: "a", Valid: true}, {}}
// tags.Slice[1].Valid == false
var scores trino.NullMap[string, trino.NullSlice[int64]]
err = db.QueryRow("SELECT MAP(ARRAY['a'], ARRAY[ARRAY[BIGINT '1', 2]])").Scan(&scores)
// scores.Map["a"].Slice == []int64{1, 2}
Elements, map keys and values, and row fields can be bool, string,
int64 and the narrower integer types, float64, float32, time.Time,
[]byte, map[string]interface{}, []interface{} or interface{}, the
nullable types from database/sql (sql.NullBool, sql.NullString,
sql.NullInt64, sql.NullInt32, sql.NullInt16, sql.NullFloat64,
sql.NullTime), trino.NullTime, trino.NullBinary, trino.Variant,
another generic scanner, or any other type that implements sql.Scanner,
whose Scan receives the element as listed in
Response rows. A NULL element scanned into a plain type
that cannot hold it, like int64, is an error. An integer element also
scans into a floating point type, and so does a DECIMAL element, which is
a string otherwise. MAP keys without a time zone, like DATE keys, are
parsed in the zone of the Location field of trino.NullMap, or of the
trino.NullSlice or trino.NullMap it is nested in, and time.Local when
no Location is set.
trino.NullRow[T] maps each ROW field to an exported field of the struct
T, by the name in a trino:"name" struct tag, or by the Go field name,
preferring an exact match and falling back to one ignoring case. A field
tagged trino:"-" is skipped. An anonymous ROW field is named field<i>,
where i is its zero-based position, so it maps to a struct field called
Field0, Field1, and so on. A ROW field without a matching struct field
is an error; a struct field without a matching ROW field keeps its zero
value. Use trino.NullRow for nested rows too:
type Point struct {
X int64 `trino:"x"`
Label sql.NullString
}
var points trino.NullSlice[trino.NullRow[Point]]
err := db.QueryRow("SELECT ARRAY[CAST(ROW(1, 'a') AS ROW(x INTEGER, label VARCHAR)), NULL]").Scan(&points)
// points.Slice[0].Row == Point{X: 1, Label: sql.NullString{String: "a", Valid: true}}
// points.Slice[1].Valid == false
A ROW whose fields are not known ahead of time scans into a trino.Row,
which keeps the field names and converts each value the way a plain column of
that type would be converted, including nested ROW and VARIANT values:
var row trino.Row
err := db.QueryRow("SELECT CAST(ROW(1, 'a') AS ROW(x INTEGER, y VARCHAR))").Scan(&row)
// row.Valid == true
// row.Len() == 2
// row.Name(0) == "x", row.Value(0) == int64(1)
// row.Field("y") == "a", true
An ARRAY or MAP scanned into an interface{} holds a []interface{} or
a map[string]interface{} whose elements are converted the way a plain
column would be: a VARBINARY element is a []byte, a number an int64 or
a float64, a DECIMAL a string and a TIMESTAMP a time.Time.
sql.ColumnType.ScanType() reports the generic scanners, instantiated with
the scan types of the elements, keys and values, like
trino.NullSlice[sql.NullInt32] for an ARRAY(INTEGER),
trino.NullMap[sql.NullString, sql.NullInt64] for a
MAP(VARCHAR, BIGINT) and trino.NullSlice[trino.Row] for an
ARRAY(ROW(...)), so a value created with reflect.New from it can be
passed to Scan(). A ROW reports trino.Row, as trino.NullRow needs a
struct type. The reported types cover arrays of up to three dimensions, maps
of scalar values, and arrays of such maps; a column nested deeper reports
interface{} for the values below the deepest level covered, like
trino.NullMap[sql.NullString, interface{}] for a
MAP(VARCHAR, ARRAY(BIGINT)). A map key that is not comparable in Go, like
VARBINARY, is reported as interface{}.
VARIANT
To read a VARIANT value, scan into a trino.Variant:
var v trino.Variant
err := db.QueryRow(`SELECT CAST(JSON '{"a": 1, "b": [true, null]}' AS VARIANT)`).Scan(&v)
// v.Valid == true
// v.Type() == trino.VariantObject
// v.Value() == map[string]interface{}{"a": int64(1), "b": []interface{}{true, nil}}
// v.String() == `{"a":1,"b":[true,null]}`
The driver announces the VARIANT_BINARY client capability, so the server
sends each value in its binary encoding and Variant keeps the value's
type. Type returns one of the Variant* constants and Value converts the
value into a Go value:
nilfor aVARIANTnullboolint64for every integer widthfloat32andfloat64trino.Numericfor a decimal, in plain notation like-12.345string, and a string like12151fd2-7586-11e9-8f9e-2a86e4085a59for a UUID[]bytetime.Timefor dates, times and timestamps. Those without a time zone are in the zone of the connection, likeDATE,TIMEandTIMESTAMPcolumns, and timestamps with a time zone are in UTC[]interface{}for an array andmap[string]interface{}for an object, holding converted values
String and MarshalJSON return the JSON text the server returns for
CAST(v AS JSON), writing types JSON has no equivalent for as strings,
like "2020-01-02" for a date. A SQL NULL scans with Valid set to
false, while a VARIANT null, like CAST(JSON 'null' AS VARIANT), is
valid and has the type trino.VariantNull.
A VARIANT column does not scan into a string or sql.NullString, as it
did when the driver did not announce the VARIANT capabilities and the
server sent VARIANT columns as JSON: database/sql only converts strings,
byte slices and numbers into a string. Scan into a trino.Variant and call
String, or use CAST(v AS JSON) in the query. An ARRAY(VARIANT) scans
into a trino.NullSlice[trino.Variant], or still into a
trino.NullSlice[sql.NullString] holding the JSON text of each element.
Errors
A query the server rejects or fails returns a *trino.ErrQueryFailed, whose
StatusCode is the HTTP status of the response that reported the failure
(200 for a query that failed while running) and which wraps the error
details. For a failure the server reports, the wrapped error is a
*trino.ErrTrino with the ErrorCode, ErrorName, ErrorType, SqlState,
Message, ErrorLocation and FailureInfo from the
Trino error:
rows, err := db.QueryContext(ctx, "SELECT 1 FORM dual")
var trinoErr *trino.ErrTrino
if errors.As(err, &trinoErr) {
log.Printf("%s (%d) at line %d: %s", trinoErr.ErrorName, trinoErr.ErrorCode, trinoErr.ErrorLocation.LineNumber, trinoErr.Message)
}
Other errors the driver returns:
trino.ErrQueryCancelledwhen the server reports the query was cancelled, for example from the web UI.trino.UnsupportedArgErrorfor a query argument of a type the driver cannot serialize, see Query arguments.*trino.SegmentExpiredErrorwhen a spooled segment could not be downloaded after its expiration time, see Spooling protocol.- A
*trino.ErrQueryFailedwithout a*trino.ErrTrinoinside for an HTTP error, such as401 Unauthorized, or when the retries of a request were exhausted, seerequest_retry_timeout. - A decoding error when a response is not well-formed JSON, which includes
invalid UTF-8 and a member name repeated within one object, or when a value
does not match the type of its column, naming the row and the column. A
malformed first page fails
Query, a later onerows.Err().
Cancellation and timeouts
A query runs until its results are consumed, its context is cancelled or its
timeout passes. When the context of a QueryContext or ExecContext call is
cancelled or reaches its deadline, the driver cancels the query on the server
with a DELETE request, giving it trino.DefaultCancelQueryTimeout (30s), and
rows.Err() returns the context error. Closing the rows before the last page
was read cancels the query the same way, so a program that stops reading early
does not leave the query running. A query whose context has no deadline gets
query_timeout, or trino.DefaultQueryTimeout (10h) when
that is not set.
Spooling protocol
The driver supports the Trino spooling protocol, which enables efficient retrieval of large result sets by downloading data in segments, optionally in parallel and out-of-order.
If the Trino server has the spooling protocol enabled, the driver uses it by
default with the json encoding. You can request other encodings for a query
with the encoding named argument, as one encoding or a list in order of
preference:
- Supported encodings:
json,json+lz4,json+zstd
rows, err := db.Query(query, sql.Named("encoding", "json+zstd"))
rows, err := db.Query(query, sql.Named("encoding", "json+zstd, json+lz4, json"))
While a spooled query is in progress, the driver sends periodic HEAD
requests to the current nextUri (same URL used for result pages) so the
coordinator treats the client as still active, which helps avoid query
abandonment when result consumption is slow. The server must support this
endpoint (Trino 475+); see heartbeat_interval.
Segment downloads follow redirects, which the coordinator answers them with in
the coordinator_storage_redirect and worker_proxy values of
protocol.spooling.retrieval-mode, sending the client to the storage or to a
worker. A segment request carries only the headers the server listed for the
segment, never the X-Trino-* session headers or credentials, so following
the redirect does not leak them; the redirect target needs the segment
headers, such as the encryption key, so they are kept. The CheckRedirect
policy of an HTTPClient or custom_client applies, and the default client
stops after 10 redirects.
Spooled segments stay in storage only until the expiresAt time the server
reports for each of them, so rows read too slowly can outlive their segments.
When a segment download fails after that time, the error is a
*trino.SegmentExpiredError carrying the expiration time and wrapping the
download error, so errors.As still finds the *trino.ErrQueryFailed with the
HTTP status. A download that fails before the segment expires returns the
download error as before.
Configuration options
You can tune the spooling protocol using the following parameters, passed as
sql.Named arguments to your query:
-
Spooling worker count
sql.Named("spooling_worker_count", "N")Sets the number of parallel workers used to download spooled segments. Default:5Considerations:- Increasing this value can improve throughput for large result sets, especially on high-latency networks.
- Higher values increase parallelism but may also increase memory usage.
-
Max out-of-order segments
sql.Named("max_out_of_order_segments", "N")Sets the maximum number of segments that can be downloaded and buffered out-of-order before blocking further downloads. Default:10Considerations:- Higher values increase the potential memory usage, but actual usage depends on download behavior and may be lower in practice.
- Higher values reduce the chance that one slow or stalled segment will block the download of additional segments.
- Lower values reduce memory usage but may limit parallelism and throughput.
It is not allowed to set spooling_worker_count higher than
max_out_of_order_segments; doing so results in an error. Each 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. If 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.
rows, err := db.Query(
query,
sql.Named("encoding", "json+zstd"),
sql.Named("spooling_worker_count", "8"),
sql.Named("max_out_of_order_segments", "20"),
)
Networking
Custom HTTP client
Beyond the TLS and proxy
parameters, the driver can send every request through an http.Client of your
own, with tuned connection pools, timeouts, instrumentation or an
authenticating transport. Pass it as Config.HTTPClient to NewConnector, or
register it under a name with trino.RegisterCustomClient and select it with
the custom_client DSN parameter:
foobarClient := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{
// your config here...
},
},
}
trino.RegisterCustomClient("foobar", foobarClient)
db, err := sql.Open("trino", "https://user@localhost:8443?custom_client=foobar")
The default client does not follow HTTP redirects, because a redirect would
carry the X-Trino-* headers, including extra credentials, to a host other
than the one in the DSN, and a 301, 302 or 303 response would turn the
statement POST into a GET without the query. A redirect response fails the
query instead. An HTTPClient is treated the same way, while a registered
custom_client follows its own CheckRedirect policy, which allows redirects
unless set. Spooled segment downloads follow redirects with every client, see
Spooling protocol.
A custom client can also add OpenTelemetry instrumentation. The otelhttp package provides a transport wrapper that creates spans for HTTP requests and propagates the trace ID in HTTP headers:
otelClient := &http.Client{
Transport: otelhttp.NewTransport(http.DefaultTransport),
}
trino.RegisterCustomClient("otel", otelClient)
db, err := sql.Open("trino", "https://user@localhost:8443?custom_client=otel")
Proxy
By default the driver takes its proxy from the HTTP_PROXY, HTTPS_PROXY
and NO_PROXY environment variables, as described for
http.ProxyFromEnvironment.
Requests to localhost and loopback addresses never use them. To send every
request through a specific proxy instead, set the httpProxy or
socksProxy parameter, or Config.HTTPProxy or
Config.SOCKSProxy:
db, err := sql.Open("trino", "https://user@trino.example.com:8443?httpProxy=proxy.example.com:3128")
An HTTPClient or a custom_client uses its own transport, so its proxy
must be configured there, for example with
http.ProxyURL; the driver rejects
httpProxy and socksProxy combined with either.
DNS resolution
There is no DSN parameter for DNS resolution. To resolve the coordinator's
host name differently, for example with a specific DNS server, give the
transport of an HTTPClient a dialer with its own
net.Resolver:
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, _ string) (net.Conn, error) {
return (&net.Dialer{}).DialContext(ctx, network, "10.0.0.53:53")
},
}
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.DialContext = (&net.Dialer{Resolver: resolver}).DialContext
connector, err := trino.NewConnector(&trino.Config{
ServerURI: "https://user@trino.example.com:8443",
HTTPClient: &http.Client{Transport: transport},
})
A dialer that maps host names to addresses on its own can replace
DialContext in the same way.
License
Apache License V2.0, as described in the LICENSE file.
Build
You can build the client code locally and run the unit tests, which need no server, with the following command:
go test -v -race ./...
The integration tests, which start Trino in Docker, live in the integration
module. See CONTRIBUTING.md for how to run them.
Contributing
For contributing, development, and release guidelines, see CONTRIBUTING.md.