The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Getting started with RTrino

RTrino is a DBI backend for Trino, the distributed SQL query engine. It talks to a Trino coordinator over the cluster’s HTTP REST API, so there is no driver to install and nothing to compile.

The code in this vignette is not evaluated: every example needs a reachable cluster.

Connecting

Trino’s REST API is stateless, so a connection is really a bundle of settings: the coordinator’s address, who you are, and which catalog and schema unqualified table names resolve against. catalog and schema are required.

library(DBI)
library(RTrino)

con <- dbConnect(
  Trino(),
  host = "http://localhost",
  port = 8080,
  catalog = "hive",
  schema = "default"
)

dbConnect() calls /v1/info before returning, so a wrong address or a rejected credential fails immediately rather than on your first query. The port can also be written into the host, as in host = "localhost:8080".

Authentication

Each authentication method is a closure that attaches credentials to every request. The credentials live inside that closure, never in the global environment, and never in a slot you might print by accident.

All three need an https:// host. Over plain HTTP the credentials would travel unencrypted, so dbConnect() refuses to send them there, before anything reaches the network. Only if the connection is encrypted by other means, such as an SSH tunnel to the coordinator, set allow_http_auth = TRUE.

Basic — LDAP or a password file

con <- dbConnect(
  Trino(),
  host = "https://trino.example.com",
  port = 443,
  user = Sys.getenv("TRINO_USER"),
  catalog = "hive",
  schema = "analytics",
  auth = trino_auth_basic(
    Sys.getenv("TRINO_USER"),
    Sys.getenv("TRINO_PASSWORD")
  )
)

JWT — service accounts and pipelines

con <- dbConnect(
  Trino(),
  host = "https://trino.example.com",
  port = 443,
  user = Sys.getenv("TRINO_USER"),
  catalog = "hive",
  schema = "analytics",
  auth = trino_auth_jwt(Sys.getenv("TRINO_TOKEN"))
)

OAuth2 client credentials — corporate SSO

httr2 caches the token and renews it when it expires, so a long-running session does not need to think about the token’s lifetime.

con <- dbConnect(
  Trino(),
  host = "https://trino.example.com",
  port = 443,
  user = Sys.getenv("TRINO_USER"),
  catalog = "hive",
  schema = "analytics",
  auth = trino_auth_oauth2(
    client_id     = Sys.getenv("OAUTH_CLIENT_ID"),
    client_secret = Sys.getenv("OAUTH_CLIENT_SECRET"),
    token_url     = "https://auth.example.com/oauth/token"
  )
)

Internal certificate authorities

A cluster whose certificate is signed by your own CA does not need certificate checking turned off. Point trino_ssl() at the CA’s PEM file and verification keeps working:

con <- dbConnect(
  Trino(),
  host = "https://trino.internal",
  port = 443,
  catalog = "hive",
  schema = "analytics",
  auth = trino_auth_basic(
    Sys.getenv("TRINO_USER"),
    Sys.getenv("TRINO_PASSWORD")
  ),
  ssl_options = trino_ssl(ca_bundle = "/etc/ssl/certs/internal-ca.pem")
)

trino_ssl(verify = FALSE) exists for a development cluster with a throwaway certificate. It accepts any certificate, including one presented by an attacker, so dbConnect() warns when it opens a connection with it.

Querying

sales <- dbGetQuery(con, "SELECT * FROM sales LIMIT 100")

For a result too large to hold in memory, take it in chunks. dbFetch() follows Trino’s pagination for you and buffers whatever a page delivered beyond what you asked for:

res <- dbSendQuery(con, "SELECT * FROM sales")
while (!dbHasCompleted(res)) {
  chunk <- dbFetch(res, n = 10000)
  # summarise, write out, accumulate...
}
dbClearResult(res)

Clearing a result that is still running sends a DELETE to the coordinator, so abandoning a query also stops the cluster working on it. So does interrupting R while it waits for a query.

dbColumnInfo() and dbFetch(res, n = 0) describe the result’s columns, with their types, before any row has been fetched.

Statements

dbExecute() runs a statement to completion and returns how many rows Trino says it changed: the count of an INSERT, UPDATE, DELETE, MERGE or CREATE TABLE ... AS SELECT, and 0 for a statement that changes no rows, such as CREATE TABLE or DROP TABLE.

dbExecute(con, "INSERT INTO memory.default.visits VALUES (1, 'a'), (2, 'b')")
#> [1] 2

RTrino has no parameter binding, and passing params is an error rather than being ignored. Put values into a statement with sqlInterpolate() or glue::glue_sql(): both quote them through dbQuoteLiteral(), which writes Trino literals of the right type, so a date is compared as a date and a string cannot break out of its quotes.

sql <- sqlInterpolate(
  con,
  "SELECT * FROM orders WHERE orderdate >= ?since AND orderstatus = ?status",
  since = as.Date("1995-01-01"),
  status = "F"
)
sql
#> <SQL> SELECT * FROM orders
#>       WHERE orderdate >= DATE '1995-01-01' AND orderstatus = 'F'
dbGetQuery(con, sql)

A POSIXct is written as a TIMESTAMP in the connection’s session.timezone, to the microsecond, which is how Trino reads it back.

Writing data

dbWriteTable(), dbAppendTable() and dbCreateTable() write data frames. Trino’s protocol has no parameter binding, so rows go in as INSERT INTO ... VALUES statements with every value written as a literal, chunk_size rows at a time (1000 by default). That suits tables of thousands of rows; for bulk loads, create the table with SQL, for example CREATE TABLE ... AS SELECT ... through dbExecute().

dbWriteTable(con, "sales", data.frame(id = 1:3, label = letters[1:3]))
dbAppendTable(con, "sales", data.frame(id = 4L, label = "d"))

# Replace the table. The old one is renamed first and restored if the write
# fails, so a failure does not leave you without it.
dbWriteTable(con, "sales", data.frame(id = 9L, label = "z"), overwrite = TRUE)

Trino has no temporary tables, so temporary = TRUE is an error, and dplyr::copy_to() and compute() need temporary = FALSE and a table name.

Transactions

dbBegin() starts a Trino transaction that every later statement runs in, until dbCommit() or dbRollback() ends it. dbWithTransaction() does both for you, rolling back if the code fails:

dbWithTransaction(con, {
  dbExecute(con, "INSERT INTO sales VALUES (5, 'e')")
  dbExecute(con, "INSERT INTO sales VALUES (6, 'f')")
})

Whether a rollback undoes a write is up to the connector behind the catalog, not RTrino: Iceberg and Delta Lake tables support it, memory and tpch do not. dbBegin() warns when the connection’s catalog is one of those.

Timeouts

A query is a series of HTTP requests, and two limits apply:

In a Shiny app, where a blocked R session blocks its user, set both:

con <- dbConnect(
  Trino(),
  host = "https://trino.example.com",
  port = 443,
  catalog = "hive",
  schema = "analytics",
  auth = trino_auth_jwt(Sys.getenv("TRINO_TOKEN")),
  timeout = 30,
  query_max_run_time = "5m"
)

Big integers and precision

Trino’s BIGINT spans values an R double cannot hold exactly. By default RTrino returns such columns as bit64::integer64, which is exact:

dbGetQuery(con, "SELECT 9007199254740993 AS id")$id
#> integer64
#> [1] 9007199254740993

The one exception is the lowest BIGINT, -9223372036854775808, which bit64 keeps for its own NA: it comes back as NA, with a warning. Choose a different representation at connection time with bigint = "numeric" (a double, exact only up to 2^53) or bigint = "character" (the exact digits, always).

DOUBLE values arrive bit for bit, NaN and the infinities included. DECIMAL is returned as a double, so a decimal with more than 15 significant digits is rounded; cast it to VARCHAR in the query to keep every digit. TIMESTAMP and TIME keep their full precision, down to the microsecond.

dplyr

With dbplyr 2.6.0 or newer installed, a connection carries a Trino SQL dialect and dplyr verbs are translated to SQL Trino accepts:

library(dplyr)

tbl(con, "sales") |>
  filter(year == 2026) |>
  group_by(region) |>
  summarise(total = sum(amount, na.rm = TRUE)) |>
  collect()

Trino-specific translations are applied so that an expression computes in Trino what it computes in R:

Where there is no faithful translation, a few differences remain, and it is worth knowing them before moving a computation from R to the cluster:

compute() needs temporary = FALSE and a name, because Trino has no temporary tables.

filter_out(), added in dplyr 1.2.0, is translated with Trino’s native IS DISTINCT FROM instead of the portable CASE WHEN comparison dbplyr falls back to, so the rows where the condition is NA are kept and the SQL stays readable.

Two groups of dplyr 1.2.0 functions have no SQL translation in dbplyr 2.6.0 and therefore none here either: when_any()/when_all(), which pass through untranslated and reach the cluster as an unknown function, and recode_values()/replace_values()/replace_when(), which fail with “Cannot translate a <formula> object to SQL”. Use | and & in place of when_any() and when_all(), and case_when() or if_else() in place of the recoding family, until dbplyr covers them.

Inspect the generated SQL before running it with dplyr::show_query(), and the cluster’s plan with dplyr::explain(). simulate_trino() gives a connection that dispatches the Trino dialect without a cluster, for checking a translation offline.

Introspection

dbListTables(con)
dbListFields(con, "sales")
dbExistsTable(con, "sales")
dbGetInfo(con)$db.version

A table can be named as "table", "schema.table" or "catalog.schema.table", with Id(catalog = , schema = , table = ), or with an identifier that is already quoted. Trino folds every identifier to lower case, so dbExistsTable(con, "SALES") finds sales, as SELECT * FROM SALES would.

Session properties and other headers

Anything the protocol expects in a header can be added to every request through extra.headers — Trino session properties, for instance. query_max_run_time joins whatever properties are set here:

con <- dbConnect(
  Trino(),
  host = "http://localhost",
  port = 8080,
  catalog = "hive",
  schema = "default",
  extra.headers = list("X-Trino-Session" = "query_max_run_time=2h")
)

Disconnecting

There is no server-side session to close, so dbDisconnect() only invalidates the object; any later use of it is an error rather than a silent reconnect.

dbDisconnect(con)

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.