diff --git a/DESCRIPTION b/DESCRIPTION index 4215e1b6..9d583c63 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -73,6 +73,7 @@ Collate: 'driver-sqlite.R' 'driver-teradata.R' 'driver-vertica.R' + 'odbc-config.R' 'odbc-data-sources.R' 'odbc-data-type.R' 'odbc-drivers.R' diff --git a/NAMESPACE b/NAMESPACE index 71f9bf31..caece594 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -29,6 +29,7 @@ export(odbcConnectionColumns) export(odbcConnectionIcon) export(odbcDataType) export(odbcListColumns) +export(odbcListConfig) export(odbcListDataSources) export(odbcListDrivers) export(odbcListObjectTypes) diff --git a/NEWS.md b/NEWS.md index 580134c2..c0695c9d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,11 @@ # odbc (development version) +* Added a function `odbcListConfig()` to help locate configuration files on + macOS and Linux (@simonpcouch, #565). + * Use correct parent class for Oracle (#685). + # odbc 1.4.0 ## Major changes diff --git a/R/odbc-config.R b/R/odbc-config.R new file mode 100644 index 00000000..08d46303 --- /dev/null +++ b/R/odbc-config.R @@ -0,0 +1,57 @@ +#' List locations of ODBC configuration files +#' +#' @description +#' On MacOS and Linux, odbc uses the unixODBC driver manager to manage +#' information about driver and data sources. This helper returns the filepaths +#' where the driver manager will look for that information. +#' +#' This function is a wrapper around the command line call `odbcinst -j`. +#' +#' Windows does not use `.ini` configuration files; this function will return a +#' 0-length vector on Windows. +#' +#' @seealso +#' The [odbcListDrivers()] and [odbcListDataSources()] helpers return +#' information on the contents of `odbcinst.ini` and `odbc.ini` files, +#' respectively. +#' +#' Learn more about unixODBC and the `odbcinst` utility +#' [here](https://www.unixodbc.org/odbcinst.html). +#' +#' @examplesIf FALSE +#' configs <- odbcListConfig() +#' +#' file.edit(configs[1]) +#' @export +odbcListConfig <- function() { + if (is_windows()) { + return(character(0)) + } + + if (!has_odbc()) { + abort( + c("The unixODBC driver manager is not available. ", + "Please install and try again.") + ) + } + + res <- system("odbcinst -j", intern = TRUE) + res <- res[grepl("\\.ini", res)] + res <- strsplit(res, "\\:") + + if (!identical(vapply(res, length, numeric(1)), c(2, 2, 2))) { + abort("Failed to parse output from odbcinst.", .internal = TRUE) + } + + res <- vapply(res, `[[`, character(1), 2) + res <- trimws(res) + names(res) <- c("drivers", "system_dsn", "user_dsn") + + res +} + +system <- NULL + +has_odbc <- function() { + !identical(unname(Sys.which("odbcinst")), "") +} diff --git a/R/utils.R b/R/utils.R index 4ca2b1a6..e4a192cb 100644 --- a/R/utils.R +++ b/R/utils.R @@ -112,3 +112,7 @@ escapePattern <- function(x, charsToEsc = c("_"), escChar = "\\\\") { x <- gsub(pattern, replace, x) I(x) } + +is_windows <- function() { + identical(.Platform$OS.type, "windows") +} diff --git a/R/zzz.R b/R/zzz.R index fd5f30c9..655482d9 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -5,11 +5,9 @@ } .onLoad <- function(libname, pkgname) { - is_windows <- identical(.Platform$OS.type, "windows") - # If TZDIR is not set we need to set it to R's timezone database on windows, # we can use the standard timezone database elsewhere. - if (is_windows) { + if (is_windows()) { if (!nzchar(Sys.getenv("TZDIR"))) Sys.setenv("TZDIR" = file.path(R.home(), "share", "zoneinfo")) } } diff --git a/inst/diagrams.key b/inst/diagrams.key index 0c06c495..b9e89bee 100644 Binary files a/inst/diagrams.key and b/inst/diagrams.key differ diff --git a/man/figures/r-interface.png b/man/figures/r-interface.png index b6401058..367e7c8e 100644 Binary files a/man/figures/r-interface.png and b/man/figures/r-interface.png differ diff --git a/man/odbcListConfig.Rd b/man/odbcListConfig.Rd new file mode 100644 index 00000000..eba7f949 --- /dev/null +++ b/man/odbcListConfig.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/odbc-config.R +\name{odbcListConfig} +\alias{odbcListConfig} +\title{List locations of ODBC configuration files} +\usage{ +odbcListConfig() +} +\description{ +On MacOS and Linux, odbc uses the unixODBC driver manager to manage +information about driver and data sources. This helper returns the filepaths +where the driver manager will look for that information. + +This function is a wrapper around the command line call \code{odbcinst -j}. + +Windows does not use \code{.ini} configuration files; this function will return a +0-length vector on Windows. +} +\examples{ +\dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +configs <- odbcListConfig() + +file.edit(configs[1]) +\dontshow{\}) # examplesIf} +} +\seealso{ +The \code{\link[=odbcListDrivers]{odbcListDrivers()}} and \code{\link[=odbcListDataSources]{odbcListDataSources()}} helpers return +information on the contents of \code{odbcinst.ini} and \code{odbc.ini} files, +respectively. + +Learn more about unixODBC and the \code{odbcinst} utility +\href{https://www.unixodbc.org/odbcinst.html}{here}. +} diff --git a/tests/testthat/_snaps/SQLServer.md b/tests/testthat/_snaps/SQLServer.md deleted file mode 100644 index 5a232d17..00000000 --- a/tests/testthat/_snaps/SQLServer.md +++ /dev/null @@ -1,8 +0,0 @@ -# Create / write to temp table - - Temporary flag is set to true, but table name doesn't use # prefix - ---- - - Temporary flag is set to true, but table name doesn't use # prefix - diff --git a/tests/testthat/_snaps/show.md b/tests/testthat/_snaps/dbi-connection.md similarity index 100% rename from tests/testthat/_snaps/show.md rename to tests/testthat/_snaps/dbi-connection.md diff --git a/tests/testthat/_snaps/odbc-config.md b/tests/testthat/_snaps/odbc-config.md new file mode 100644 index 00000000..68a990e0 --- /dev/null +++ b/tests/testthat/_snaps/odbc-config.md @@ -0,0 +1,29 @@ +# odbcListConfig errors informatively without unixODBC + + Code + odbcListConfig() + Condition + Error in `odbcListConfig()`: + ! The unixODBC driver manager is not available. + * Please install and try again. + +# odbcListConfig errors informatively with unexpected odbcinst output + + Code + odbcListConfig() + Condition + Error in `odbcListConfig()`: + ! Failed to parse output from odbcinst. + i This is an internal error that was detected in the odbc package. + Please report it at with a reprex () and the full backtrace. + +--- + + Code + odbcListConfig() + Condition + Error in `odbcListConfig()`: + ! Failed to parse output from odbcinst. + i This is an internal error that was detected in the odbc package. + Please report it at with a reprex () and the full backtrace. + diff --git a/tests/testthat/_snaps/Connection.md b/tests/testthat/_snaps/odbc-connection.md similarity index 100% rename from tests/testthat/_snaps/Connection.md rename to tests/testthat/_snaps/odbc-connection.md diff --git a/tests/testthat/test-odbc-config.R b/tests/testthat/test-odbc-config.R new file mode 100644 index 00000000..0617fcee --- /dev/null +++ b/tests/testthat/test-odbc-config.R @@ -0,0 +1,37 @@ +test_that("odbcListConfig returns appropriate result", { + skip_on_os(c("windows", "solaris")) + skip_if(!has_odbc(), "odbcinst not available.") + + res <- odbcListConfig() + + expect_type(res, "character") + expect_length(res, 3) + expect_named(res, c("drivers", "system_dsn", "user_dsn")) + expect_match(res, "\\.ini") +}) + +test_that("odbcListConfig returns an empty vector on Windows", { + local_mocked_bindings(is_windows = function() {TRUE}) + + res <- odbcListConfig() + + expect_equal(res, character(0)) +}) + +test_that("odbcListConfig errors informatively without unixODBC", { + local_mocked_bindings(is_windows = function() {FALSE}, + has_odbc = function() {FALSE}) + + expect_snapshot(error = TRUE, odbcListConfig()) +}) + +test_that("odbcListConfig errors informatively with unexpected odbcinst output", { + local_mocked_bindings(is_windows = function() {FALSE}, + has_odbc = function() {TRUE}) + + local_mocked_bindings(system = function(...) {c("beep", "bop")}) + expect_snapshot(error = TRUE, odbcListConfig()) + + local_mocked_bindings(system = function(...) {""}) + expect_snapshot(error = TRUE, odbcListConfig()) +})