Skip to content

API refactor + Oracle fixes #577

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 186 additions & 15 deletions R/Connection.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#' @include Driver.R
NULL

setClassUnion("missingOrNULL", c("missing", "NULL"))

#' Supported Connection Attributes
#'
#' These (pre) connection attributes are supported and can be passed as
Expand Down Expand Up @@ -133,6 +135,8 @@ setClass(
#' - char_octet_length
#' - ordinal_position
#' - nullable
#'
#' @rdname odbcConnectionColumns
#' @export
setGeneric(
"odbcConnectionColumns",
Expand All @@ -150,8 +154,8 @@ setMethod(
c("OdbcConnection", "Id"),
function(conn, name, column_name = NULL) {

connection_sql_columns(conn@ptr,
table_name = id_field(name, "table"),
odbcConnectionColumns(conn,
name = id_field(name, "table"),
catalog_name = id_field(name, "catalog"),
schema_name = id_field(name, "schema"),
column_name = column_name)
Expand Down Expand Up @@ -191,6 +195,184 @@ setMethod(
odbcConnectionColumns(conn, dbUnquoteIdentifier(conn, name)[[1]], ...)
})

#' odbcConnectionTables
#'
#' This function returns a listing of tables accessible
#' to the connected user.
#' The expectation is that this is a relatively thin
#' wrapper around the ODBC `SQLTables` function call,
#' albeit returning a subset of the fields.
#'
#' It is important to note that, similar to the ODBC/API
#' call, this method also accomodates pattern-value arguments
#' for the catalog, schema, and table name arguments.
#'
#' If extending this method, be aware that `package:odbc`
#' internally uses this method to satisfy both
#' DBI::dbListTables and DBI::dbExistsTable methods.
#' ( The former also advertises pattern value arguments )
#'
#' @param conn OdbcConnection
#' @param name table we wish to search for
#' @param ... additional parameters to methods
#'
#' @seealso The ODBC documentation on [SQLTables](https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlcolumns-function)
#' for further details.
#'
#' @return data.frame with columns
#' - table_catalog
#' - table_schema
#' - table_name
#' - table_remarks
setGeneric(
"odbcConnectionTables",
valueClass = "data.frame",
function(conn, name, ...) {
standardGeneric("odbcConnectionTables")
}
)

#' @rdname odbcConnectionTables
#' @param table_type List tables of this type, for example 'VIEW'.
#' See odbcConnectionTableTypes for a listing of available table
#' types for your connection.
setMethod(
"odbcConnectionTables",
c("OdbcConnection", "Id"),
function(conn, name, table_type = NULL) {

odbcConnectionTables(conn,
name = id_field(name, "table"),
catalog_name = id_field(name, "catalog"),
schema_name = id_field(name, "schema"),
table_type = table_type)
}
)

#' @rdname odbcConnectionTables
#' @param catalog_name character catalog where we wish to query for
#' available tables
#' @param schema_name character schema where we wish to query for
#' available tables.
setMethod(
"odbcConnectionTables",
c("OdbcConnection", "character"),
function(conn, name, catalog_name = NULL, schema_name = NULL, table_type = NULL) {

connection_sql_tables(conn@ptr,
catalog_name = catalog_name,
schema_name = schema_name,
table_name = name,
table_type = table_type)

}
)

setMethod(
"odbcConnectionTables",
c("OdbcConnection", "missingOrNULL"),
function(conn, name, catalog_name = NULL, schema_name = NULL, table_type = NULL) {

odbcConnectionTables(conn,
name = "%",
catalog_name = catalog_name,
schema_name = schema_name,
table_type = table_type)

}
)

#' @rdname odbcConnectionTables
setMethod(
"odbcConnectionTables", c("OdbcConnection", "SQL"),
function(conn, name, table_type = NULL) {
odbcConnectionTables(conn, dbUnquoteIdentifier(conn, name)[[1]], ...)
})

#' odbcConnectionCatalogs
#'
#' This function returns a listing of available
#' catalogs.
#' @param conn OdbcConnection
#' @rdname odbcConnectionCatalogs
setGeneric(
"odbcConnectionCatalogs",
valueClass = "character",
function(conn) {
standardGeneric("odbcConnectionCatalogs")
}
)

#' @rdname odbcConnectionCatalogs
setMethod(
"odbcConnectionCatalogs",
c("OdbcConnection"),
function(conn) {
connection_sql_catalogs(conn@ptr)
}
)

#' odbcConnectionSchemas
#'
#' This function returns a listing of available
#' schemas.
#'
#' Currently, for a generic connection the
#' catalog_name argument is ignored.
#'
#' @param conn OdbcConnection
#' @param catalog_name Catalog where
#' we are looking to list schemas.
#' @rdname odbcConnectionSchemas
setGeneric(
"odbcConnectionSchemas",
valueClass = "character",
function(conn, catalog_name) {
standardGeneric("odbcConnectionSchemas")
}
)

#' @rdname odbcConnectionSchemas
setMethod(
"odbcConnectionSchemas",
c("OdbcConnection", "missingOrNULL"),
function(conn, catalog_name) {
connection_sql_schemas(conn@ptr)
}
)

#' @rdname odbcConnectionSchemas
setMethod(
"odbcConnectionSchemas",
c("OdbcConnection", "character"),
function(conn, catalog_name) {
connection_sql_schemas(conn@ptr)
}
)

#' odbcConnectionTableTypes
#'
#' This function returns a listing of table
#' types available in database.
#' @param conn OdbcConnection
#' @rdname odbcConnectionTableTypes
setGeneric(
"odbcConnectionTableTypes",
valueClass = "character",
function(conn) {
standardGeneric("odbcConnectionTableTypes")
}
)

#' @rdname odbcConnectionTableTypes
setMethod(
"odbcConnectionTableTypes",
"OdbcConnection",
function(conn) {
connection_sql_table_types(conn@ptr)
}
)

# TODO: show encoding, timezone, bigint mapping
#' @rdname OdbcConnection
#' @inheritParams methods::show
Expand Down Expand Up @@ -330,24 +512,13 @@ setMethod(
function(conn, catalog_name = NULL, schema_name = NULL, table_name = NULL,
table_type = NULL, ...) {

connection_sql_tables(conn@ptr,
odbcConnectionTables(conn,
name = table_name,
catalog_name = catalog_name,
schema_name = schema_name,
table_name = table_name,
table_type = table_type)$table_name
})

#' @rdname OdbcConnection
#' @inheritParams DBI::dbExistsTable
#' @export
setMethod(
"dbExistsTable", c("OdbcConnection", "character"),
function(conn, name, ...) {
stopifnot(length(name) == 1)
df <- connection_sql_tables(conn@ptr, table_name = name)
NROW(df) > 0
})

#' @inherit DBI::dbListFields
#' @inheritParams DBI::dbListFields
#' @aliases dbListFields
Expand Down
12 changes: 12 additions & 0 deletions R/RcppExports.R
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ connection_sql_tables <- function(p, catalog_name = NULL, schema_name = NULL, ta
.Call(`_odbc_connection_sql_tables`, p, catalog_name, schema_name, table_name, table_type)
}

connection_sql_catalogs <- function(p) {
.Call(`_odbc_connection_sql_catalogs`, p)
}

connection_sql_schemas <- function(p) {
.Call(`_odbc_connection_sql_schemas`, p)
}

connection_sql_table_types <- function(p) {
.Call(`_odbc_connection_sql_table_types`, p)
}

connection_sql_columns <- function(p, column_name = NULL, catalog_name = NULL, schema_name = NULL, table_name = NULL) {
.Call(`_odbc_connection_sql_columns`, p, column_name, catalog_name, schema_name, table_name)
}
Expand Down
17 changes: 14 additions & 3 deletions R/Table.R
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,10 @@ createFields <- function(con, fields, field.types, row.names) {
setMethod(
"dbExistsTable", c("OdbcConnection", "Id"),
function(conn, name, ...) {
name@name[["table"]] %in% connection_sql_tables(conn@ptr,
name@name[["table"]] %in% odbcConnectionTables(conn,
name = id_field(name, "table"),
catalog_name = id_field(name, "catalog"),
schema_name = id_field(name, "schema"),
table_name = id_field(name, "table")
schema_name = id_field(name, "schema")
)
})

Expand All @@ -222,3 +222,14 @@ setMethod(
function(conn, name, ...) {
dbExistsTable(conn, dbUnquoteIdentifier(conn, name)[[1]], ...)
})

#' @rdname OdbcConnection
#' @inheritParams DBI::dbExistsTable
#' @export
setMethod(
"dbExistsTable", c("OdbcConnection", "character"),
function(conn, name, ...) {
stopifnot(length(name) == 1)
df <- odbcConnectionTables(conn, name = name)
NROW(df) > 0
})
24 changes: 12 additions & 12 deletions R/Viewer.R
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,19 @@ odbcListObjectTypes.default <- function(connection) {
obj_types <- list(table = list(contains = "data"))

# see if we have views too
table_types <- string_values(connection_sql_tables(connection@ptr, "", "", "", "%")[["table_type"]])
table_types <- string_values(odbcConnectionTableTypes(connection))
if (any(table_types == "VIEW")) {
obj_types <- c(obj_types, list(view = list(contains = "data")))
}

# check for multiple schema or a named schema
schemas <- string_values(connection_sql_tables(connection@ptr, "", "%", "", "")[["table_schema"]])
schemas <- string_values(odbcConnectionSchemas(connection))
if (length(schemas) > 0) {
obj_types <- list(schema = list(contains = obj_types))
}

# check for multiple catalogs
catalogs <- string_values(connection_sql_tables(connection@ptr, "%", "", "", "")[["table_catalog"]])
catalogs <- string_values(odbcConnectionCatalogs(connection))
if (length(catalogs) > 0) {
obj_types <- list(catalog = list(contains = obj_types))
}
Expand Down Expand Up @@ -84,7 +84,8 @@ odbcListObjects.OdbcConnection <- function(connection, catalog = NULL, schema =
# if no catalog was supplied but this database has catalogs, return a list of
# catalogs
if (is.null(catalog)) {
catalogs <- string_values(connection_sql_tables(connection@ptr, catalog_name = "%", "", "", NULL)[["table_catalog"]])
catalogs <- tryCatch(
string_values(odbcConnectionCatalogs(connection)), error = function(e) vector(mode = "character"))
if (length(catalogs) > 0) {
return(
data.frame(
Expand All @@ -98,7 +99,7 @@ odbcListObjects.OdbcConnection <- function(connection, catalog = NULL, schema =
# if no schema was supplied but this database has schema, return a list of
# schema
if (is.null(schema)) {
schemas <- string_values(connection_sql_tables(connection@ptr, "", "%", "", NULL)[["table_schema"]])
schemas <- string_values(odbcConnectionSchemas(connection, catalog))
if (length(schemas) > 0) {
return(
data.frame(
Expand All @@ -109,7 +110,7 @@ odbcListObjects.OdbcConnection <- function(connection, catalog = NULL, schema =
}
}

objs <- tryCatch(connection_sql_tables(connection@ptr, catalog, schema, name, table_type = type), error = function(e) NULL)
objs <- tryCatch(odbcConnectionTables(connection, name, catalog, schema, table_type = type), error = function(e) NULL)
# just return a list of the objects and their types, possibly filtered by the
# options above
data.frame(
Expand Down Expand Up @@ -192,8 +193,8 @@ odbcListColumns.OdbcConnection <- function(connection, table = NULL, view = NULL
catalog = NULL, schema = NULL, ...) {

# specify schema or catalog if given
cols <- connection_sql_columns(connection@ptr,
table_name = validateObjectName(table, view),
cols <- odbcConnectionColumns(connection,
name = validateObjectName(table, view),
catalog_name = catalog,
schema_name = schema)

Expand Down Expand Up @@ -328,7 +329,7 @@ odbcConnectionActions.default <- function(connection) {
function(e) identical(get(e, envir = .GlobalEnv), connection),
ls(envir = .GlobalEnv))

tables <- connection_sql_tables(connection@ptr)
tables <- odbcConnectionTables(connection)
columnPos <- 6
if (nrow(tables) == 0) {
contents <- paste(
Expand All @@ -346,12 +347,12 @@ odbcConnectionActions.default <- function(connection) {
tableName <- dbQuoteIdentifier(connection, firstTable$table_name)

# add schema
if (!is.null(firstTable$table_schema) && nchar(firstTable$table_schema) > 0) {
if (!is.null(firstTable$table_schema) && !is.na(firstTable$table_schema) && nchar(firstTable$table_schema) > 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This level of type flexibility always makes me a bit nervous. Could we add some type checks higher up so we only need to check for one empty sentinel?

Copy link
Collaborator Author

@detule detule Jun 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Hadley:

As best as I can tell, the is.null(...) that was there to begin-with is not guarding anything; I think only the is.na is actually effective.

Kind of hesitant to remove the is.null since I am not sure why it was there in the first place. But at the same time this is not in any critical code-path; we can revisit if removing it breaks something.

tableName <- paste(dbQuoteIdentifier(connection, firstTable$table_schema), tableName, sep = ".")
}

# add catalog
if (!is.null(firstTable$table_catalog) && nchar(firstTable$table_catalog) > 0) {
if (!is.null(firstTable$table_catalog) && !is.na(firstTable$table_catalog) && nchar(firstTable$table_catalog) > 0) {
tableName <- paste(dbQuoteIdentifier(connection, firstTable$table_catalog), tableName, sep = ".")
}

Expand All @@ -365,7 +366,6 @@ odbcConnectionActions.default <- function(connection) {

columnPos <- 14
}
tables <- dbListTables(connection)

documentNew("sql", contents, row = 2, column = columnPos, execute = FALSE)
}
Expand Down
Loading