#' Create a lazy query backed by a database
#'
#' @description
#' Use `tbl()` to create a SQL query backed by a database. Manipulating this
#' object with dplyr verbs then builds up a SQL query that will only be executed
#' when you explicitly ask for it, either by printing the object, calling
#' [collect()] to bring the data back to R or calling [compute()] to create a
#' new table in the database. You can see the query without executing it with
#' [show_query()].
#'
#' Learn more in `vignette("dbplyr")`.
#'
#' @param src A `DBIConnection` object produced by [DBI::dbConnect()].
#' @param from Either a table identifier or a literal [sql()] string.
#'
#'   Use a string to identify a table in the current schema/catalog or
#'   `I()` for a table elsewhere, e.g. `I("schema.table")` or
#'   `I("catalog.schema.table")`. For backward compatibility, you can also use
#'   [in_schema()]/[in_catalog()] or [DBI::Id()].
#' @param vars Optionally, provide a character vector of column names. If
#'   not supplied, will be retrieved from the database by running a simple
#'   query. This argument is mainly useful for better performance when creating
#'   many `tbl`s with known variables.
#' @param ... Passed on to [tbl_sql()]
#' @export
#' @examples
#' library(dplyr)
#'
#' # Connect to a temporary in-memory SQLite database and add some data
#' con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:")
#' copy_to(con, mtcars)
#'
#' # To retrieve a single table from a source, use `tbl()`
#' mtcars_db <- con |> tbl("mtcars")
#' mtcars_db
#'
#' # Use `I()` for qualified table names
#' con |> tbl(I("temp.mtcars")) |> head(1)
#'
#' # You can also pass raw SQL if you want a more sophisticated query
#' con |> tbl(sql("SELECT * FROM mtcars WHERE cyl = 8")) |> head(1)
#'
#' # But in most cases, you'll rely on dbplyr to construct the SQL:
#' mtcars_db |>
#'   filter(vs == 1) |>
#'   summarise(mpg = mean(mpg, na.rm = TRUE), .by = cyl) |>
#'   show_query()
#'
#' @importFrom dplyr tbl
#' @aliases tbl_dbi
tbl.src_dbi <- function(src, from, vars = NULL, ...) {
  check_dots_empty()
  db_table(src$con, from, vars = vars)
}

db_table <- function(
  con,
  from,
  vars = NULL,
  subclass = NULL,
  call = caller_env()
) {
  check_con(con)
  source <- as_table_source(from, con = con, error_call = call)

  check_character(vars, allow_null = TRUE, call = call)
  vars <- vars %||% find_variables(con, from, call = call)

  new_tbl_sql(con = con, source = source, vars = vars, subclass = subclass)
}

#' Database src
#'
#' @description
#' `r lifecycle::badge("superseded")`
#'
#' Since can generate a `tbl()` directly from a DBI connection we no longer
#' recommend using `src_dbi()`.
#'
#' @param con An object that inherits from [DBI::DBIConnection-class],
#'   typically generated by [DBI::dbConnect]
#' @param auto_disconnect Should the connection be automatically closed when
#'   the src is deleted? Set to `TRUE` if you initialize the connection
#'   the call to `src_dbi()`. Pass `NA` to auto-disconnect but print a message
#'   when this happens.
#' @return An S3 object with class `src_dbi`, `src_sql`, `src`.
#' @keywords internal
#' @export
src_dbi <- function(con, auto_disconnect = FALSE) {
  # Avoid registering disconnector if con can't be evaluated
  force(con)

  # stopifnot(is(con, "DBIConnection"))
  if (is_false(auto_disconnect)) {
    disco <- NULL
  } else {
    disco <- db_disconnector(con, quiet = is_true(auto_disconnect)) # nocov
  }

  structure(
    list(
      con = con,
      disco = disco
    ),
    class = connection_s3_class(con)
  )
}

connection_s3_class <- function(con) {
  subclass <- setdiff(methods::is(con), methods::extends("DBIConnection"))
  c(paste0("src_", subclass), "src_dbi", "src_sql", "src")
}

methods::setOldClass(c("src_dbi", "src_sql", "src"))

# nocov start
# Creates an environment that disconnects the database when it's GC'd
db_disconnector <- function(con, quiet = FALSE) {
  reg.finalizer(environment(), function(...) {
    if (!quiet) {
      message("Auto-disconnecting ", class(con)[[1]])
    }
    DBI::dbDisconnect(con)
  })
  environment()
}
# nocov end
