R/dbExistsTable.R

#' Does a table exist?
#'
#' Asks `information_schema` for a count, so the answer costs the same whatever
#' the schema holds. Trino folds every identifier to lower case, so the name is
#' matched the same way: `"SALES"` exists if `sales` does, as
#' `SELECT * FROM SALES` would find it.
#'
#' @param conn A [TrinoConnection-class] object.
#' @param name Table name, required. A string: `"table"` resolves against the
#'   connection's catalog and schema, `"schema.table"` keeps its catalog, and
#'   `"catalog.schema.table"` is used as given. Also a [DBI::Id()], with
#'   components named `catalog`, `schema` and `table`, or a quoted identifier
#'   from [DBI::SQL()] or [DBI::dbQuoteIdentifier()].
#' @param ... Unused, for compatibility with the generic.
#'
#' @return A logical scalar. A name in a catalog or schema that does not exist
#'   is simply `FALSE`, not an error.
#' @export
#' @examplesIf nzchar(Sys.getenv("RTRINO_TEST_URL"))
#' con <- DBI::dbConnect(RTrino::Trino(), host = Sys.getenv("RTRINO_TEST_URL"),
#'                       catalog = "tpch", schema = "tiny")
#' DBI::dbExistsTable(con, "nation")
#' DBI::dbExistsTable(con, "tpch.sf1.nation")
#' DBI::dbExistsTable(con, DBI::Id(catalog = "tpch", schema = "sf1",
#'                                 table = "nation"))
#' DBI::dbExistsTable(con, "no_such_table")
#' DBI::dbDisconnect(con)
setMethod(
  "dbExistsTable", c("TrinoConnection", "character"),
  function(conn, name, ...) {
    trino_check_valid(conn)
    parts <- trino_table_parts(conn, name)

    # Counting one row in information_schema rather than listing the schema and
    # matching in R: the coordinator applies the predicate and a single row
    # comes back, so the answer costs the same whatever the schema holds.
    # Measured against Trino 483, listing cost +0.005 ms per table in the
    # schema (36.5 ms at 1200 tables) while this stays flat at ~28 ms.
    #
    # information_schema.tables, not .columns: existence does not need the
    # column metadata, which the connector would have to resolve and this would
    # then discard.
    #
    # information_schema holds names in lower case, Trino's folding of every
    # identifier; lower() on the literal is folded to a constant when the query
    # is planned, so the predicate is still pushed down to the connector.
    sql <- paste0(
      "SELECT count(*) AS n FROM ",
      dbQuoteIdentifier(conn, parts[[1L]]), ".information_schema.tables",
      " WHERE table_schema = lower(", dbQuoteString(conn, parts[[2L]]), ")",
      " AND table_name = lower(", dbQuoteString(conn, parts[[3L]]), ")"
    )

    # A table in a catalog that does not exist does not exist either: DBI asks
    # for a logical here, not an error. Any other failure still propagates.
    out <- tryCatch(
      dbGetQuery(conn, sql),
      trino_query_error = function(cnd) {
        if (identical(cnd$error_name, "CATALOG_NOT_FOUND")) {
          return(NULL)
        }
        stop(cnd)
      }
    )
    !is.null(out) && nrow(out) == 1L && as.numeric(out[[1L]]) > 0
  }
)

#' @rdname dbExistsTable-TrinoConnection-character-method
#' @export
setMethod(
  "dbExistsTable", c("TrinoConnection", "Id"),
  function(conn, name, ...) {
    dbExistsTable(conn, DBI::SQL(trino_qualify(conn, name)), ...)
  }
)

#' @rdname dbExistsTable-TrinoConnection-character-method
#' @export
setMethod(
  "dbExistsTable", c("TrinoConnection", "ANY"),
  function(conn, name, ...) trino_bad_table_name(name, "the table to look up")
)

Try the RTrino package in your browser

Any scripts or data that you put into this service are public.

RTrino documentation built on Oct. 15, 2026, 5:08 p.m.