R/dbi-s3.r

Defines functions src_desc sql_translate_env sql_translate_env.NULL db_list_tables db_list_tables.DBIConnection db_has_table db_has_table.DBIConnection db_data_type db_data_type.DBIConnection db_save_query db_save_query.DBIConnection db_begin db_begin.DBIConnection db_commit db_commit.DBIConnection db_rollback db_rollback.DBIConnection db_create_table db_create_table.DBIConnection db_insert_into db_create_indexes db_create_indexes.DBIConnection db_create_index db_create_index.DBIConnection db_drop_table db_drop_table.DBIConnection db_analyze db_analyze.DBIConnection db_explain db_explain.DBIConnection db_query_fields db_query_fields.DBIConnection db_query_rows db_query_rows.DBIConnection random_table_name db_disconnector res_warn_incomplete

Documented in db_analyze db_begin db_commit db_create_index db_create_indexes db_create_table db_data_type db_drop_table db_explain db_has_table db_insert_into db_list_tables db_query_fields db_query_rows db_rollback db_save_query sql_translate_env sql_translate_env.NULL src_desc

#' @import DBI
NULL


#' Source generics.
#'
#' These generics retrieve metadata for a given src.
#'
#' @keywords internal
#' @name backend_src
NULL

#' @name backend_src
#' @export
src_desc <- function(x) UseMethod("src_desc")

#' @name backend_src
#' @export
sql_translate_env <- function(con) UseMethod("sql_translate_env")

#' @name backend_src
#' @export
sql_translate_env.NULL <- function(con) {
  sql_variant(
    base_scalar,
    base_agg,
    base_win
  )
}

#' Database generics.
#'
#' These generics execute actions on the database. Most generics have a method
#' for \code{DBIConnection} which typically just call the standard DBI S4
#' method.
#'
#' Note, a few backend methods do not call the standard DBI S4 methods including
#' \itemize{
#' \item \code{db_data_type}: Calls DBI's \code{dbDataType} for every field
#' (e.g. data frame column) and returns a vector of corresponding SQL data
#' types
#' \item \code{db_save_query}: Builds and executes \code{CREATE [TEMPORARY]
#' TABLE <table> ...} SQL command.
#' \item \code{db_create_table}: Builds and executes \code{CREATE [TEMPORARY]
#' TABLE <table> ...} SQL command.
#' \item \code{db_create_index}: Builds and executes \code{CREATE INDEX <name>
#' ON <table>} SQL command.
#' \item \code{db_drop_table}: Builds and executes \code{DROP TABLE [IF EXISTS]
#'  <table>} SQL command.
#' \item \code{db_analyze}: Builds and executes \code{ANALYZE <table>} SQL
#' command.
#' \item \code{db_insert_into} and \code{db_explain}: do not have methods
#' calling corresponding DBI methods. The latter because no underlying DBI S4
#' method exists and the former because calls to the corresponding DBI S4
#' method (\code{dbWriteTable}) need to be able to specify an appropriate
#' combination of values for non-standard \code{append} and \code{overwrite}
#' arguments.
#' }
#'
#' Currently, \code{copy_to} is the only user of \code{db_begin()}, \code{db_commit()},
#' \code{db_rollback()}, \code{db_create_table()}, \code{db_insert_into()},
#' \code{db_create_indexes()}, \code{db_drop_table()} and
#' \code{db_analyze()}. If you find yourself overriding many of these
#' functions it may suggest that you should just override \code{\link{copy_to}}
#' instead.
#'
#' @return Usually a logical value indicating success. Most failures should generate
#'  an error. However, \code{db_has_table()} should return \code{NA} if
#'  temporary tables cannot be listed with \code{dbListTables} (due to backend
#'  API limitations for example). As a result, you methods will rely on the
#'  backend to throw an error if a table exists when it shouldn't.
#' @name backend_db
#' @param con A database connection.
#' @keywords internal
NULL

#' @name backend_db
#' @export
db_list_tables <- function(con) UseMethod("db_list_tables")
#' @export
db_list_tables.DBIConnection <- function(con) dbListTables(con)

#' @name backend_db
#' @export
#' @param table A string, the table name.
db_has_table <- function(con, table) UseMethod("db_has_table")
#' @export
db_has_table.DBIConnection <- function(con, table) dbExistsTable(con, table)

#' @name backend_db
#' @export
#' @param fields A list of fields, as in a data frame.
db_data_type <- function(con, fields) UseMethod("db_data_type")
#' @export
db_data_type.DBIConnection <- function(con, fields) {
  vapply(fields, dbDataType, dbObj = con, FUN.VALUE = character(1))
}

#' @name backend_db
#' @export
db_save_query <- function(con, sql, name, temporary = TRUE, ...) {
  UseMethod("db_save_query")
}

#' @export
db_save_query.DBIConnection <- function(con, sql, name, temporary = TRUE,
                                        ...) {
  tt_sql <- build_sql("CREATE ", if (temporary) sql("TEMPORARY "),
    "TABLE ", ident(name), " AS ", sql, con = con)
  dbGetQuery(con, tt_sql)
  name
}

#' @name backend_db
#' @export
db_begin <- function(con, ...) UseMethod("db_begin")
#' @export
db_begin.DBIConnection <- function(con, ...) {
  dbBegin(con)
}

#' @name backend_db
#' @export
db_commit <- function(con, ...) UseMethod("db_commit")
#' @export
db_commit.DBIConnection <- function(con, ...) dbCommit(con)

#' @name backend_db
#' @export
db_rollback <- function(con, ...) UseMethod("db_rollback")
#' @export
db_rollback.DBIConnection <- function(con, ...) dbRollback(con)

#' @name backend_db
#' @export
db_create_table <- function(con, table, types, temporary = FALSE, ...) {
  UseMethod("db_create_table")
}
#' @export
db_create_table.DBIConnection <- function(con, table, types,
                                           temporary = FALSE, ...) {
  assert_that(is.string(table), is.character(types))

  field_names <- escape(ident(names(types)), collapse = NULL, con = con)
  fields <- sql_vector(paste0(field_names, " ", types), parens = TRUE,
    collapse = ", ", con = con)
  sql <- build_sql("CREATE ", if (temporary) sql("TEMPORARY "),
    "TABLE ", ident(table), " ", fields, con = con)

  dbGetQuery(con, sql)
}

#' @name backend_db
#' @export
db_insert_into <- function(con, table, values, ...) {
  UseMethod("db_insert_into")
}

#' @name backend_db
#' @export
db_create_indexes <- function(con, table, indexes = NULL, unique = FALSE, ...) {
  UseMethod("db_create_indexes")
}

#' @export
db_create_indexes.DBIConnection <- function(con, table, indexes = NULL,
  unique = FALSE, ...) {
  if (is.null(indexes)) return()
  assert_that(is.list(indexes))

  for(index in indexes) {
    db_create_index(con, table, index, unique = unique, ...)
  }
}

#' @name backend_db
#' @export
db_create_index <- function(con, table, columns, name = NULL, unique = FALSE,
                            ...) {
  UseMethod("db_create_index")
}

#' @export
db_create_index.DBIConnection <- function(con, table, columns, name = NULL,
                                          unique = FALSE, ...) {
  assert_that(is.string(table), is.character(columns))

  name <- name %||% paste0(c(table, columns), collapse = "_")
  fields <- escape(ident(columns), parens = TRUE, con = con)
  sql <- build_sql(
    "CREATE ", if (unique) sql("UNIQUE "), "INDEX ", ident(name),
    " ON ", ident(table), " ", fields,
    con = con)

  dbGetQuery(con, sql)
}

#' @name backend_db
#' @export
db_drop_table <- function(con, table, force = FALSE, ...) {
  UseMethod("db_drop_table")
}
#' @export
db_drop_table.DBIConnection <- function(con, table, force = FALSE, ...) {
  sql <- build_sql("DROP TABLE ", if (force) sql("IF EXISTS "), ident(table),
    con = con)
  dbGetQuery(con, sql)
}

#' @name backend_db
#' @export
db_analyze <- function(con, table, ...) UseMethod("db_analyze")
#' @export
db_analyze.DBIConnection <- function(con, table, ...) {
  sql <- build_sql("ANALYZE ", ident(table), con = con)
  dbGetQuery(con, sql)
}

#' @export
#' @rdname backend_db
db_explain <- function(con, sql, ...) {
  UseMethod("db_explain")
}

#' @export
db_explain.DBIConnection <- function(con, sql, ...) {
  exsql <- build_sql("EXPLAIN ", sql, con = con)
  expl <- dbGetQuery(con, exsql)
  out <- utils::capture.output(print(expl))

  paste(out, collapse = "\n")
}

#' @rdname backend_db
#' @export
db_query_fields <- function(con, sql, ...) {
  UseMethod("db_query_fields")
}
#' @export
db_query_fields.DBIConnection <- function(con, sql, ...) {
  sql <- sql_select(con, sql("*"), sql_subquery(con, sql), where = sql("0 = 1"))
  qry <- dbSendQuery(con, sql)
  on.exit(dbClearResult(qry))

  res <- dbFetch(qry, 0)
  names(res)
}

#' @rdname backend_db
#' @export
db_query_rows <- function(con, sql, ...) {
  UseMethod("db_query_rows")
}
#' @export
db_query_rows.DBIConnection <- function(con, sql, ...) {
  from <- sql_subquery(con, sql, "master")
  rows <- build_sql("SELECT count(*) FROM ", from, con = con)

  as.integer(dbGetQuery(con, rows)[[1]])
}

# Utility functions ------------------------------------------------------------

random_table_name <- function(n = 10) {
  paste0(sample(letters, n, replace = TRUE), collapse = "")
}

# Creates an environment that disconnects the database when it's
# garbage collected
db_disconnector <- function(con, name, quiet = FALSE) {
  reg.finalizer(environment(), function(...) {
    if (!quiet) {
      message("Auto-disconnecting ", name, " connection ",
        "(", paste(con@Id, collapse = ", "), ")")
    }
    dbDisconnect(con)
  })
  environment()
}

res_warn_incomplete <- function(res, hint = "n = -1") {
  if (dbHasCompleted(res)) return()

  rows <- big_mark(dbGetRowCount(res))
  warning("Only first ", rows, " results retrieved. Use ", hint, " to retrieve all.",
    call. = FALSE)
}
sctyner/dplyr050 documentation built on May 17, 2019, 2:22 p.m.