diff --git a/R/utils.R b/R/utils.R index 21270e5e..ae3322f9 100644 --- a/R/utils.R +++ b/R/utils.R @@ -142,3 +142,9 @@ is_windows <- function() { } compact <- function(x) x[!vapply(x, is.null, logical(1))] + +random_name <- function(prefix = "") { + vals <- c(letters, LETTERS, 0:9) + name <- paste0(sample(vals, 10, replace = TRUE), collapse = "") + paste0(prefix, "odbc_", name) +} diff --git a/tests/testthat/_snaps/driver-sql-server.md b/tests/testthat/_snaps/driver-sql-server.md index 5a232d17..de6be803 100644 --- a/tests/testthat/_snaps/driver-sql-server.md +++ b/tests/testthat/_snaps/driver-sql-server.md @@ -1,4 +1,23 @@ -# Create / write to temp table +# dbWriteTable errors if field.types don't exist (#271) + + Code + sqlCreateTable(con, "foo", iris, field.types = list(bar = "[int]")) + Condition + Warning: + Some columns in `field.types` not in the input, missing columns: + - 'bar' + Output + CREATE TABLE "foo" ( + "Sepal.Length" FLOAT, + "Sepal.Width" FLOAT, + "Petal.Length" FLOAT, + "Petal.Width" FLOAT, + "Species" varchar(255), + "bar" [int] + ) + + +# can create / write to temp table Temporary flag is set to true, but table name doesn't use # prefix diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index 334f035b..c9f1f62d 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -9,6 +9,21 @@ test_connection_string <- function(db) { list(.connection_string = cs) } +test_con <- function(db, ...) { + dbConnect( + odbc::odbc(), + .connection_string = test_connection_string(db), + ... + ) +} + +local_table <- function(con, name, df, ..., envir = parent.frame()) { + dbWriteTable(con, name, df, ...) + withr::defer(dbRemoveTable(con, name), envir = envir) + + name +} + skip_if_no_drivers <- function() { if (nrow(odbcListDrivers()) == 0) { skip("No drivers installed") @@ -44,7 +59,7 @@ skip_if_no_drivers <- function() { #' # Only test a specific column #' test_roundtrip(con, "integer", invert = FALSE) #' } -test_roundtrip <- function(con = DBItest:::connect(DBItest::get_default_context()), columns = "", invert = TRUE, force_sorted = FALSE) { +test_roundtrip <- function(con, columns = "", invert = TRUE, force_sorted = FALSE) { dbms <- dbGetInfo(con)$dbms.name res <- list() testthat::test_that(paste0("[", dbms, "] round tripping data.frames works"), { diff --git a/tests/testthat/test-driver-mysql.R b/tests/testthat/test-driver-mysql.R index 35fdabcc..78e14121 100644 --- a/tests/testthat/test-driver-mysql.R +++ b/tests/testthat/test-driver-mysql.R @@ -81,33 +81,38 @@ test_that("MySQL", { "reexport", NULL )) +}) + +test_that("can roundtrip columns", { + test_roundtrip(test_con("MYSQL"), columns = c("logical", "binary")) +}) - test_roundtrip(columns = c("logical", "binary")) - test_that("odbcPreviewObject", { - tblName <- "test_preview" - con <- DBItest:::connect(DBItest:::get_default_context()) - dbWriteTable(con, tblName, data.frame(a = 1:10L)) - on.exit(dbRemoveTable(con, tblName)) - # There should be no "Pending rows" warning - expect_no_warning({ - res <- odbcPreviewObject(con, rowLimit = 3, table = tblName) - }) - expect_equal(nrow(res), 3) +test_that("odbcPreviewObject", { + con <- test_con("MYSQL") + tbl <- local_table(con, "test_preview", data.frame(a = 1:10L)) + + # There should be no "Pending rows" warning + expect_no_warning({ + res <- odbcPreviewObject(con, rowLimit = 3, table = tbl) }) - test_that("sproc result retrieval", { - sprocName <- "testSproc" - con <- DBItest:::connect(DBItest:::get_default_context()) - DBI::dbExecute( - con, - paste0("CREATE PROCEDURE ", sprocName, "(IN arg INT) BEGIN SELECT 'abc' as TestCol; END") - ) - on.exit(DBI::dbExecute(con, paste0("DROP PROCEDURE ", sprocName))) - expect_no_error({ - res <- dbGetQuery(con, paste0("CALL ", sprocName, "(1)")) - }) - expect_identical( - res, - data.frame("TestCol" = "abc", stringsAsFactors = FALSE) - ) + expect_equal(nrow(res), 3) +}) + +test_that("sproc result retrieval", { + con <- test_con("MYSQL") + + sprocName <- "testSproc" + DBI::dbExecute( + con, + paste0("CREATE PROCEDURE ", sprocName, "(IN arg INT) BEGIN SELECT 'abc' as TestCol; END") + ) + on.exit(DBI::dbExecute(con, paste0("DROP PROCEDURE ", sprocName))) + + expect_no_error({ + res <- dbGetQuery(con, paste0("CALL ", sprocName, "(1)")) }) + expect_identical( + res, + data.frame("TestCol" = "abc", stringsAsFactors = FALSE) + ) }) diff --git a/tests/testthat/test-driver-oracle.R b/tests/testthat/test-driver-oracle.R index f9759e99..210cb514 100644 --- a/tests/testthat/test-driver-oracle.R +++ b/tests/testthat/test-driver-oracle.R @@ -1,6 +1,5 @@ -test_that("Oracle", { - - con <- dbConnect(odbc::odbc(), .connection_string = test_connection_string("ORACLE")) +test_that("can round columns", { + con <- test_con("ORACLE") # - Long/outstanding issue with batch inserting # date/datetime for Oracle. See for example # #349, #350, #391 @@ -8,17 +7,14 @@ test_that("Oracle", { # to binary elements of size zero. # - Finally, no boolean in Oracle prior to 23 test_roundtrip(con, columns = c("time", "date", "datetime", "binary", "logical")) +}) + +test_that("can detect existence of table", { + con <- test_con("ORACLE") + + tbl1 <- local_table(con, "mtcarstest", mtcars) + expect_true(dbExistsTable(con, tbl1)) - local({ - # Test custom dbExistsTable implementation for - # Oracle - dbWriteTable(con, "mtcarstest", mtcars) - expect_true(dbExistsTable(con, "mtcarstest")) - dbWriteTable(con, "mtcars_test", mtcars) - expect_true(dbExistsTable(con, "mtcars_test")) - on.exit({ - dbRemoveTable(con, "mtcarstest") - dbRemoveTable(con, "mtcars_test") - }) - }) + tbl2 <- local_table(con, "mtcars_test", mtcars) + expect_true(dbExistsTable(con, tbl2)) }) diff --git a/tests/testthat/test-driver-postgres.R b/tests/testthat/test-driver-postgres.R index eea41c53..3ce3ff63 100644 --- a/tests/testthat/test-driver-postgres.R +++ b/tests/testthat/test-driver-postgres.R @@ -6,77 +6,6 @@ test_that("PostgreSQL", { name = "PostgreSQL" ) - test_that("show method works as expected with real connection", { - skip_on_os("windows") - con <- DBItest:::connect(DBItest:::get_default_context()) - - expect_output(show(con), "@localhost") - expect_output(show(con), "Database: [a-z]+") - expect_output(show(con), "PostgreSQL Version: ") - }) - - test_that("64 bit integers work with alternate mappings", { - con_default <- DBItest:::connect(DBItest:::get_default_context()) - con_integer64 <- - DBItest:::connect(DBItest:::get_default_context(), bigint = "integer64") - con_integer <- - DBItest:::connect(DBItest:::get_default_context(), bigint = "integer") - con_numeric <- - DBItest:::connect(DBItest:::get_default_context(), bigint = "numeric") - con_character <- - DBItest:::connect(DBItest:::get_default_context(), bigint = "character") - - dbWriteTable(con_default, "test", data.frame(a = 1:10L), field.types = c(a = "BIGINT")) - on.exit(dbRemoveTable(con_default, "test")) - - expect_s3_class(dbReadTable(con_default, "test")$a, "integer64") - expect_s3_class(dbReadTable(con_integer64, "test")$a, "integer64") - - expect_type(dbReadTable(con_integer, "test")$a, "integer") - - expect_type(dbReadTable(con_numeric, "test")$a, "double") - - expect_type(dbReadTable(con_character, "test")$a, "character") - }) - - # This test checks whether when writing to a table and using - # result_describe_parameters to offer descriptions of the data - # we are attempting to write, our logic remains robust to the - # case when the data being written has columns ordered - # differently than the table we are targetting. - test_that("Writing data.frame with column ordering different than target table", { - tblName <- "test_order_write" - con <- DBItest:::connect(DBItest:::get_default_context()) - values <- data.frame( - datetime = as.POSIXct(c(14, 15), origin = "2016-01-01", tz = "UTC"), - name = c("one", "two"), - num = 1:2, - stringsAsFactors = FALSE - ) - sql <- sqlCreateTable(con, tblName, values) - dbExecute(con, sql) - on.exit(dbRemoveTable(con, tblName)) - dbWriteTable(con, tblName, values[c(2, 3, 1)], - overwrite = FALSE, append = TRUE - ) - received <- DBI::dbReadTable(con, tblName) - received <- received[order(received$num), ] - row.names(received) <- NULL - expect_equal(values, received) - }) - - test_that("odbcPreviewObject", { - tblName <- "test_preview" - con <- DBItest:::connect(DBItest:::get_default_context()) - dbWriteTable(con, tblName, data.frame(a = 1:10L)) - on.exit(dbRemoveTable(con, tblName)) - # There should be no "Pending rows" warning - expect_no_warning({ - res <- odbcPreviewObject(con, rowLimit = 3, table = tblName) - }) - expect_equal(nrow(res), 3) - }) - DBItest::test_getting_started(c( "package_name", # Not an error NULL @@ -163,6 +92,73 @@ test_that("PostgreSQL", { "reexport", # TODO NULL )) +}) + +test_that("can roundtrip columns", { + con <- test_con("POSTGRES") + test_roundtrip(con) +}) + +test_that("show method works as expected with real connection", { + skip_on_os("windows") + con <- test_con("POSTGRES") + + expect_output(show(con), "@localhost") + expect_output(show(con), "Database: [a-z]+") + expect_output(show(con), "PostgreSQL Version: ") +}) + +test_that("64 bit integers work with alternate mappings", { + con_integer64 <- test_con("POSTGRES", bigint = "integer64") + con_integer <- test_con("POSTGRES", bigint = "integer") + con_numeric <- test_con("POSTGRES", bigint = "numeric") + con_character <- test_con("POSTGRES", bigint = "character") + + tbl <- local_table( + con_integer64, + "test", + data.frame(a = 1:10L), + field.types = c(a = "BIGINT") + ) + + expect_s3_class(dbReadTable(con_integer64, tbl)$a, "integer64") + expect_type(dbReadTable(con_integer, tbl)$a, "integer") + expect_type(dbReadTable(con_numeric, tbl)$a, "double") + expect_type(dbReadTable(con_character, tbl)$a, "character") +}) + +# This test checks whether when writing to a table and using +# result_describe_parameters to offer descriptions of the data +# we are attempting to write, our logic remains robust to the +# case when the data being written has columns ordered +# differently than the table we are targetting. +test_that("Writing data.frame with column ordering different than target table", { + con <- test_con("POSTGRES") + values <- data.frame( + datetime = as.POSIXct(c(14, 15), origin = "2016-01-01", tz = "UTC"), + name = c("one", "two"), + num = 1:2, + stringsAsFactors = FALSE + ) + tbl <- "test_order_write" + dbCreateTable(con, tbl, values) + dbAppendTable(con, tbl, values[c(2, 3, 1)]) + on.exit(dbRemoveTable(con, tbl)) + + received <- dbReadTable(con, tbl) + received <- received[order(received$num), ] + row.names(received) <- NULL + expect_equal(values, received) +}) + +test_that("odbcPreviewObject", { + con <- test_con("POSTGRES") + tbl <- local_table(con, "test_preview", data.frame(a = 1:10L)) - test_roundtrip() + # There should be no "Pending rows" warning + expect_no_warning({ + res <- odbcPreviewObject(con, rowLimit = 3, table = tbl) + }) + expect_equal(nrow(res), 3) }) + diff --git a/tests/testthat/test-driver-sql-server.R b/tests/testthat/test-driver-sql-server.R index 37dbe991..409dc2b4 100644 --- a/tests/testthat/test-driver-sql-server.R +++ b/tests/testthat/test-driver-sql-server.R @@ -79,271 +79,258 @@ test_that("SQLServer", { "reexport", NULL )) +}) - local({ - # SQLServer works with schemas (#197) - con <- DBItest:::connect(DBItest:::get_default_context()) - dbExecute(con, "DROP SCHEMA IF EXISTS testSchema") - dbExecute(con, "CREATE SCHEMA testSchema") - on.exit({ - dbExecute(con, "DROP TABLE testSchema.iris") - dbExecute(con, "DROP SCHEMA testSchema") - }) - - ir <- iris - ir$Species <- as.character(ir$Species) - - table_id <- Id(schema = "testSchema", table = "iris") - dbWriteTable(conn = con, name = table_id, value = ir) - dbWriteTable(conn = con, name = table_id, value = ir, append = TRUE) - - res <- dbReadTable(con, table_id) - expect_equal(res, rbind(ir, ir)) - - dbWriteTable(conn = con, name = table_id, value = ir, overwrite = TRUE) - res <- dbReadTable(con, table_id) - expect_equal(res, ir) - - # Test: We can enumerate schemas out of catalog ( #527 ) - # Part 1: Make sure we can see the schema we created in the - # current catalog. - res <- odbcConnectionSchemas(con) - expect_true("testSchema" %in% res) - # Part 2: Make sure we don't see that schema in the tempdb - # listing ( out of catalog schema listing ) - res <- odbcConnectionSchemas(con, catalog_name = "tempdb") - # Should, at least, have INFORMATION_SCHEMA and sys - expect_true(length(res) > 1) - expect_false("testSchema" %in% res) +test_that("works with schemas (#197)", { + con <- test_con("SQLSERVER") + dbExecute(con, "DROP SCHEMA IF EXISTS testSchema") + dbExecute(con, "CREATE SCHEMA testSchema") + on.exit({ + dbExecute(con, "DROP TABLE testSchema.iris") + dbExecute(con, "DROP SCHEMA testSchema") }) - local({ - # SQLServer works with dbAppendTable (#215) - con <- DBItest:::connect(DBItest:::get_default_context()) + ir <- iris + ir$Species <- as.character(ir$Species) + + table_id <- Id(schema = "testSchema", table = "iris") + dbWriteTable(conn = con, name = table_id, value = ir) + dbWriteTable(conn = con, name = table_id, value = ir, append = TRUE) + + res <- dbReadTable(con, table_id) + expect_equal(res, rbind(ir, ir)) + + dbWriteTable(conn = con, name = table_id, value = ir, overwrite = TRUE) + res <- dbReadTable(con, table_id) + expect_equal(res, ir) + + # Test: We can enumerate schemas out of catalog ( #527 ) + # Part 1: Make sure we can see the schema we created in the + # current catalog. + res <- odbcConnectionSchemas(con) + expect_true("testSchema" %in% res) + # Part 2: Make sure we don't see that schema in the tempdb + # listing ( out of catalog schema listing ) + res <- odbcConnectionSchemas(con, catalog_name = "tempdb") + # Should, at least, have INFORMATION_SCHEMA and sys + expect_true(length(res) > 1) + expect_false("testSchema" %in% res) +}) - ir <- iris - ir$Species <- as.character(ir$Species) +test_that("works with dbAppendTable (#215)", { + con <- test_con("SQLSERVER") - dbWriteTable(con, "iris", ir) - on.exit(dbRemoveTable(con, "iris")) + ir <- iris + ir$Species <- as.character(ir$Species) - dbAppendTable(conn = con, name = "iris", value = ir) + tbl <- local_table(con, "iris", ir) + dbAppendTable(con, tbl, ir) - res <- dbReadTable(con, "iris") - expect_equal(res, rbind(ir, ir)) - }) + res <- dbReadTable(con, tbl) + expect_equal(res, rbind(ir, ir)) +}) - local({ - # Subseconds are retained upon insertion (#208) - con <- DBItest:::connect(DBItest:::get_default_context()) +test_that("Subseconds are retained upon insertion (#208)", { + con <- test_con("SQLSERVER") + data <- data.frame(time = Sys.time()) + tbl <- local_table(con, "time", data) - data <- data.frame(time = Sys.time()) - dbWriteTable(con, "time", data, field.types = list(time = "DATETIME"), overwrite = TRUE) - on.exit(dbRemoveTable(con, "time")) - res <- dbReadTable(con, "time") + res <- dbReadTable(con, tbl) + expect_equal(as.double(res$time), as.double(data$time)) +}) - expect_equal(as.double(res$time), as.double(data$time)) - }) +test_that("dbWriteTable errors if field.types don't exist (#271)", { + con <- test_con("SQLSERVER") - local({ - # dbWriteTable errors if field.types don't exist (#271) - con <- DBItest:::connect(DBItest:::get_default_context()) + expect_snapshot( + sqlCreateTable(con, "foo", iris, field.types = list(bar = "[int]")) + ) +}) - on.exit(dbRemoveTable(con, "foo"), add = TRUE) - expect_warning( - dbWriteTable(con, "foo", iris, field.types = list(bar = "[int]")), - "Some columns in `field.types` not in the input, missing columns:" - ) - }) +test_that("blobs can be retrieved out of order", { + con <- test_con("SQLSERVER") + values <- data.frame( + c1 = 1, + c2 = "this is varchar max", + c3 = 11, + c4 = "this is text", + stringsAsFactors = FALSE + ) + tbl <- local_table(con, "test_out_of_order_blob", values) - local({ - con <- DBItest:::connect(DBItest:::get_default_context()) - tblName <- "test_out_of_order_blob" - - values <- data.frame( - c1 = 1, - c2 = "this is varchar max", - c3 = 11, - c4 = "this is text", - stringsAsFactors = FALSE - ) - dbWriteTable(con, tblName, values, field.types = list(c1 = "INT", c2 = "VARCHAR(MAX)", c3 = "INT", c4 = "TEXT")) - on.exit(dbRemoveTable(con, tblName)) - received <- DBI::dbReadTable(con, tblName) - # Also test retrival using a prepared statement - received2 <- dbGetQuery(con, - paste0("SELECT * FROM ", tblName, " WHERE c1 = ?"), - params = list(1L) - ) - expect_equal(values, received) - expect_equal(values, received2) - }) + received <- DBI::dbReadTable(con, tbl) + expect_equal(received, values) - local({ - con <- DBItest:::connect(DBItest:::get_default_context()) - tblName <- "test_na" - # With SELECT ing with the OEM SQL Server driver, everything - # after the first column should be unbound. Test null detection for - # unbound columns (NULL is registered after a call to nanodbc::result::get) - values <- data.frame( - c1 = c("this is varchar max", NA_character_), - c2 = c(1L, NA_integer_), - c3 = c(1.0, NA_real_), - c4 = c(TRUE, NA), - c5 = c(Sys.Date(), NA), - c6 = c(Sys.time(), NA), - stringsAsFactors = FALSE - ) - dbWriteTable(con, tblName, values, field.types = list(c1 = "VARCHAR(MAX)", c2 = "INT", c3 = "FLOAT", c4 = "BIT", c5 = "DATE", c6 = "DATETIME")) - on.exit(dbRemoveTable(con, tblName)) - received <- DBI::dbReadTable(con, tblName) - expect_equal(values[-6], received[-6]) - expect_equal(as.double(values[[6]]), as.double(received[[6]])) - }) + # Also test retrival using a prepared statement + received2 <- dbGetQuery(con, + paste0("SELECT * FROM ", tbl, " WHERE c1 = ?"), + params = list(1L) + ) + expect_equal(received2, values) +}) - local({ - con <- DBItest:::connect(DBItest:::get_default_context()) - input <- DBI::SQL(c( - "testtable", - "[testtable]", - "[\"testtable\"]", - "testta[ble", - "testta]ble", - "[testschema].[testtable]", - "[testschema].testtable", - "[testdb].[testschema].[testtable]", - "[testdb].[testschema].testtable" - )) - expected <- c( - DBI::Id("testtable"), - DBI::Id("testtable"), - DBI::Id("testtable"), - DBI::Id("testta[ble"), - DBI::Id("testta]ble"), - DBI::Id("testschema", "testtable"), - DBI::Id("testschema", "testtable"), - DBI::Id("testdb", "testschema", "testtable"), - DBI::Id("testdb", "testschema", "testtable") - ) - expect_identical(DBI::dbUnquoteIdentifier(con, input), expected) - }) +test_that("can bind NA values", { + con <- test_con("SQLSERVER") + # With SELECT ing with the OEM SQL Server driver, everything + # after the first column should be unbound. Test null detection for + # unbound columns (NULL is registered after a call to nanodbc::result::get) + values <- data.frame( + c1 = c("this is varchar max", NA_character_), + c2 = c(1L, NA_integer_), + c3 = c(1.0, NA_real_), + c4 = c(TRUE, NA), + c5 = c(Sys.Date(), NA), + c6 = c(Sys.time(), NA), + stringsAsFactors = FALSE + ) + tbl <- local_table(con, "test_na", values) - test_that("odbcPreviewObject", { - tblName <- "test_preview" - con <- DBItest:::connect(DBItest:::get_default_context()) - dbWriteTable(con, tblName, data.frame(a = 1:10L)) - on.exit(dbRemoveTable(con, tblName)) - # There should be no "Pending rows" warning - expect_no_warning({ - res <- odbcPreviewObject(con, rowLimit = 3, table = tblName) - }) - expect_equal(nrow(res), 3) - }) + received <- DBI::dbReadTable(con, tbl) + expect_equal(values[-6], received[-6]) + expect_equal(as.double(values[[6]]), as.double(received[[6]])) +}) - test_that("dates should always be interpreted in the system time zone (#398)", { - con <- DBItest:::connect(DBItest:::get_default_context(), timezone = "America/Chicago") - res <- dbGetQuery(con, "SELECT CAST(? AS date)", params = as.Date("2019-01-01")) - expect_equal(res[[1]], as.Date("2019-01-01")) - }) +test_that("can parse SQL server identifiers", { + con <- test_con("SQLSERVER") + input <- DBI::SQL(c( + "testtable", + "[testtable]", + "[\"testtable\"]", + "testta[ble", + "testta]ble", + "[testschema].[testtable]", + "[testschema].testtable", + "[testdb].[testschema].[testtable]", + "[testdb].[testschema].testtable" + )) + expected <- c( + DBI::Id("testtable"), + DBI::Id("testtable"), + DBI::Id("testtable"), + DBI::Id("testta[ble"), + DBI::Id("testta]ble"), + DBI::Id("testschema", "testtable"), + DBI::Id("testschema", "testtable"), + DBI::Id("testdb", "testschema", "testtable"), + DBI::Id("testdb", "testschema", "testtable") + ) + expect_identical(DBI::dbUnquoteIdentifier(con, input), expected) +}) - test_that("UTF in VARCHAR is not truncated", { - con <- DBItest:::connect(DBItest:::get_default_context()) - value <- "grün" - res <- dbGetQuery( - con, - paste0("SELECT '", value, "' AS colone") - ) - expect_equal(value, res[[1]]) - }) +test_that("odbcPreviewObject doesn't warn about pending rows", { + con <- test_con("SQLSERVER") + tbl <- local_table(con, "test_preview", data.frame(a = 1:10L)) - test_that("Zero-row-fetch does not move cursor", { - con <- DBItest:::connect(DBItest:::get_default_context()) - tblName <- "test_zero_row_fetch" - dbWriteTable(con, tblName, mtcars[1:2, ]) - on.exit(dbRemoveTable(con, tblName)) - rs <- dbSendStatement(con, paste0("SELECT * FROM ", tblName)) - expect_equal(nrow(dbFetch(rs, n = 0)), 0) - expect_equal(nrow(dbFetch(rs, n = 10)), 2) - dbClearResult(rs) + # There should be no "Pending rows" warning + expect_no_warning({ + res <- odbcPreviewObject(con, rowLimit = 3, table = tbl) }) + expect_equal(nrow(res), 3) +}) - test_that("isTempTable tests", { - con <- DBItest:::connect(DBItest:::get_default_context()) - expect_true(isTempTable(con, "#myTmp")) - expect_true(isTempTable(con, "#myTmp", catalog_name = "tempdb")) - expect_true(isTempTable(con, "#myTmp", catalog_name = "%")) - expect_true(isTempTable(con, "#myTmp", catalog_name = NULL)) - expect_true(!isTempTable(con, "##myTmp")) - expect_true(!isTempTable(con, "#myTmp", catalog_name = "abc")) - }) +test_that("dates should always be interpreted in the system time zone (#398)", { + con <- test_con("SQLSERVER") + res <- dbGetQuery(con, "SELECT CAST(? AS date)", params = as.Date("2019-01-01")) + expect_equal(res[[1]], as.Date("2019-01-01")) +}) - test_that("dbExistsTable accounts for local temp tables", { - con <- DBItest:::connect(DBItest:::get_default_context()) - con2 <- DBItest:::connect(DBItest:::get_default_context()) - tbl_name <- "#myTemp" - tbl_name2 <- "##myTemp" - tbl_name3 <- "#myTemp2" - DBI::dbExecute(con, paste0("CREATE TABLE ", tbl_name, " ( - id int not null, - primary key (id) )"), immediate = TRUE) - expect_true(dbExistsTable(con, tbl_name)) - expect_true(dbExistsTable(con, tbl_name, catalog_name = "tempdb")) - # Fail because not recognized as temp table ( catalog not tempdb ) - expect_true(!dbExistsTable(con, tbl_name, catalog_name = "abc")) - # Fail because not recognized as temp table ( second char "#" ) - expect_true(!dbExistsTable(con, tbl_name2, catalog_name = "tempdb")) - # Fail because table not actually present - expect_true(!dbExistsTable(con, tbl_name3, catalog_name = "tempdb")) - # Fail because table was created in another live session - expect_true(!dbExistsTable(con2, tbl_name)) - }) +test_that("UTF in VARCHAR is not truncated", { + con <- test_con("SQLSERVER") + value <- "grün" + res <- dbGetQuery( + con, + paste0("SELECT '", value, "' AS colone") + ) + expect_equal(value, res[[1]]) +}) - test_that("Create / write to temp table", { - testthat::local_edition(3) - con <- DBItest:::connect(DBItest:::get_default_context()) - locTblName <- "#myloctmp" - globTblName <- "##myglobtmp" - notTempTblName <- "nottemp" - - df <- data.frame(name = c("one", "two"), value = c(1, 2)) - values <- sqlData(con, row.names = FALSE, df[, , drop = FALSE]) - ret1 <- sqlCreateTable(con, locTblName, values, temporary = TRUE) - ret2 <- sqlCreateTable(con, locTblName, values, temporary = FALSE) - - nm <- dbQuoteIdentifier(con, locTblName) - fields <- createFields(con, values, row.names = FALSE, field.types = NULL) - expected <- DBI::SQL(paste0( - "CREATE TABLE ", nm, " (\n", - " ", paste(fields, collapse = ",\n "), "\n)\n" - )) - - expect_equal(ret1, expected) - expect_equal(ret2, expected) - expect_snapshot_warning(sqlCreateTable(con, globTblName, values, temporary = TRUE)) - expect_no_warning(sqlCreateTable(con, globTblName, values, temporary = FALSE)) - expect_snapshot_warning(sqlCreateTable(con, notTempTblName, values, temporary = TRUE)) - expect_no_warning(sqlCreateTable(con, notTempTblName, values, temporary = FALSE)) - - # These tests need https://github.com/r-dbi/odbc/pull/600 - # Uncomment when both merged. - # dbWriteTable(con, locTblName, mtcars, row.names = TRUE) - # res <- dbGetQuery(con, paste0("SELECT * FROM ", locTblName)) - # expect_equal( mtcars$mpg, res$mpg ) - # dbAppendTable(con, locTblName, mtcars) - # res <- dbGetQuery(con, paste0("SELECT * FROM ", locTblName)) - # expect_equal( nrow( res ), 2 * nrow( mtcars ) ) - }) +test_that("Zero-row-fetch does not move cursor", { + con <- test_con("SQLSERVER") + tbl <- local_table(con, "test_zero_row_fetch", mtcars[1:2, ]) - test_that("Multiline error message", { - tryCatch( - { - DBI::dbConnect(odbc::odbc(), dsn = "does_not_exist_db") - }, - error = function(e) { - # Expect to see at least one newline character in message - # ( previously one long string, #643 ) - expect_true(grepl("\n", e$message)) - } - ) - }) + rs <- dbSendStatement(con, paste0("SELECT * FROM ", tbl)) + expect_equal(nrow(dbFetch(rs, n = 0)), 0) + expect_equal(nrow(dbFetch(rs, n = 10)), 2) + dbClearResult(rs) +}) + +test_that("isTempTable handles variety of temporary specifications", { + con <- test_con("SQLSERVER") + expect_true(isTempTable(con, "#myTmp")) + expect_true(isTempTable(con, "#myTmp", catalog_name = "tempdb")) + expect_true(isTempTable(con, "#myTmp", catalog_name = "%")) + expect_true(isTempTable(con, "#myTmp", catalog_name = NULL)) + expect_true(!isTempTable(con, "##myTmp")) + expect_true(!isTempTable(con, "#myTmp", catalog_name = "abc")) +}) + +test_that("dbExistsTable accounts for local temp tables", { + con <- test_con("SQLSERVER") + tbl_name <- "#myTemp" + tbl_name2 <- "##myTemp" + tbl_name3 <- "#myTemp2" + DBI::dbExecute(con, paste0("CREATE TABLE ", tbl_name, " ( + id int not null, + primary key (id) )"), immediate = TRUE) + expect_true(dbExistsTable(con, tbl_name)) + expect_true(dbExistsTable(con, tbl_name, catalog_name = "tempdb")) + # Fail because not recognized as temp table ( catalog not tempdb ) + expect_true(!dbExistsTable(con, tbl_name, catalog_name = "abc")) + # Fail because not recognized as temp table ( second char "#" ) + expect_true(!dbExistsTable(con, tbl_name2, catalog_name = "tempdb")) + # Fail because table not actually present + expect_true(!dbExistsTable(con, tbl_name3, catalog_name = "tempdb")) + + # fail because table was created in another live session + con2 <- test_con("SQLSERVER") + expect_true(!dbExistsTable(con2, tbl_name)) +}) + +test_that("can create / write to temp table", { + con <- test_con("SQLSERVER") + locTblName <- "#myloctmp" + + df <- data.frame(name = c("one", "two"), value = c(1, 2)) + values <- sqlData(con, row.names = FALSE, df[, , drop = FALSE]) + + nm <- dbQuoteIdentifier(con, locTblName) + fields <- createFields(con, values, row.names = FALSE, field.types = NULL) + expected <- DBI::SQL(paste0( + "CREATE TABLE ", nm, " (\n", + " ", paste(fields, collapse = ",\n "), "\n)\n" + )) + + ret1 <- sqlCreateTable(con, locTblName, values, temporary = TRUE) + expect_equal(ret1, expected) + + ret2 <- sqlCreateTable(con, locTblName, values, temporary = FALSE) + expect_equal(ret2, expected) + + dbWriteTable(con, locTblName, mtcars, row.names = TRUE) + res <- dbGetQuery(con, paste0("SELECT * FROM ", locTblName)) + expect_equal(mtcars$mpg, res$mpg) + dbAppendTable(con, locTblName, mtcars) + res <- dbGetQuery(con, paste0("SELECT * FROM ", locTblName)) + expect_equal(nrow(res), 2 * nrow(mtcars)) + + globTblName <- "##myglobtmp" + expect_snapshot_warning(sqlCreateTable(con, globTblName, values, temporary = TRUE)) + expect_no_warning(sqlCreateTable(con, globTblName, values, temporary = FALSE)) + + notTempTblName <- "nottemp" + expect_snapshot_warning(sqlCreateTable(con, notTempTblName, values, temporary = TRUE)) + expect_no_warning(sqlCreateTable(con, notTempTblName, values, temporary = FALSE)) +}) + +test_that("captures multiline errors message", { + tryCatch( + { + DBI::dbConnect(odbc::odbc(), dsn = "does_not_exist_db") + }, + error = function(e) { + # Expect to see at least one newline character in message + # ( previously one long string, #643 ) + expect_true(grepl("\n", e$message)) + } + ) }) diff --git a/tests/testthat/test-driver-sqlite.R b/tests/testthat/test-driver-sqlite.R index fc004970..196cc02c 100644 --- a/tests/testthat/test-driver-sqlite.R +++ b/tests/testthat/test-driver-sqlite.R @@ -128,25 +128,22 @@ test_that("SQLite", { "reexport", # TODO NULL )) +}) + +test_that("unsupported types gives informative error", { + con <- test_con("SQLITE") + df <- data.frame(foo = complex(1)) + expect_error(dbWriteTable(con, "df", df), "Column 'foo' is of unsupported type: 'complex'") +}) - local({ - ## Test that trying to write unsupported types (like complex numbers) throws an - ## informative error message - con <- DBItest:::connect(DBItest:::get_default_context()) +test_that("odbcPreviewObject works", { + con <- test_con("SQLITE") + tbl <- local_table(con, "test_preview", data.frame(a = 1:10L)) - df <- data.frame(foo = complex(1)) - expect_error(dbWriteTable(con, "df", df), "Column 'foo' is of unsupported type: 'complex'") - }) - test_that("odbcPreviewObject", { - tblName <- "test_preview" - con <- DBItest:::connect(DBItest:::get_default_context()) - dbWriteTable(con, tblName, data.frame(a = 1:10L)) - on.exit(dbRemoveTable(con, tblName)) - # There should be no "Pending rows" warning - expect_no_warning({ - res <- odbcPreviewObject(con, rowLimit = 3, table = tblName) - }) - expect_equal(nrow(res), 3) + # There should be no "Pending rows" warning + expect_no_warning({ + res <- odbcPreviewObject(con, rowLimit = 3, table = tbl) }) + expect_equal(nrow(res), 3) })