tests/testthat/test-sql.R

local_edition(3)

test_that("sql_query() executes SELECT statements correctly", {
  result <- sql_query("SELECT 42 AS answer")
  expect_s3_class(result, "data.frame")
  expect_equal(ncol(result), 1)
  expect_equal(nrow(result), 1)
  expect_equal(result$answer, 42)
})

test_that("sql_query() works with multiple rows and columns", {
  result <- sql_query("SELECT 1 AS x, 'a' AS y UNION ALL SELECT 2, 'b'")
  expect_s3_class(result, "data.frame")
  expect_equal(ncol(result), 2)
  expect_equal(nrow(result), 2)
  expect_equal(result$x, c(1, 2))
  expect_equal(result$y, c("a", "b"))
})

test_that("sql_query() can access data frames as tables", {
  # Create a test data frame in the environment
  test_df <- data.frame(id = 1:3, value = c("a", "b", "c"))

  result <- sql_query("SELECT * FROM test_df ORDER BY id")
  expect_s3_class(result, "data.frame")
  expect_equal(nrow(result), 3)
  expect_equal(result$id, 1:3)
  expect_equal(result$value, c("a", "b", "c"))
})

test_that("sql_query() works with custom connection", {
  custom_con <- dbConnect(duckdb())
  result <- sql_query("SELECT 'custom' AS source", conn = custom_con)
  expect_equal(result$source, "custom")
  dbDisconnect(custom_con, shutdown = TRUE)
})

test_that("sql_query() validates connection", {
  invalid_con <- dbConnect(duckdb())
  dbDisconnect(invalid_con, shutdown = TRUE)

  expect_snapshot(error = TRUE, sql_query("SELECT 1", conn = invalid_con))
})

test_that("sql_exec() executes DDL statements correctly", {
  # Clean up any existing test table
  tryCatch(sql_exec("DROP TABLE IF EXISTS test_exec"), error = function(e) NULL)

  # Test CREATE TABLE
  rows_affected <- sql_exec("CREATE TABLE test_exec (id INTEGER, name VARCHAR)")
  expect_equal(rows_affected, 0)  # DDL statements typically return 0

  # Clean up
  sql_exec("DROP TABLE test_exec")
})

test_that("sql_exec() executes INSERT statements correctly", {
  # Clean up any existing test table
  tryCatch(sql_exec("DROP TABLE IF EXISTS test_insert"), error = function(e) NULL)

  sql_exec("CREATE TABLE test_insert (id INTEGER, name VARCHAR)")

  # Test INSERT
  rows_affected <- sql_exec("INSERT INTO test_insert VALUES (1, 'Alice'), (2, 'Bob')")
  expect_equal(rows_affected, 2)

  # Verify the data was inserted
  result <- sql_query("SELECT COUNT(*) AS count FROM test_insert")
  expect_equal(result$count, 2)

  # Clean up
  sql_exec("DROP TABLE test_insert")
})

test_that("sql_exec() executes UPDATE statements correctly", {
  # Clean up any existing test table
  tryCatch(sql_exec("DROP TABLE IF EXISTS test_update"), error = function(e) NULL)

  sql_exec("CREATE TABLE test_update (id INTEGER, name VARCHAR)")
  sql_exec("INSERT INTO test_update VALUES (1, 'Alice'), (2, 'Bob')")

  # Test UPDATE
  rows_affected <- sql_exec("UPDATE test_update SET name = 'Charlie' WHERE id = 1")
  expect_equal(rows_affected, 1)

  # Verify the update
  result <- sql_query("SELECT name FROM test_update WHERE id = 1")
  expect_equal(result$name, "Charlie")

  # Clean up
  sql_exec("DROP TABLE test_update")
})

test_that("sql_exec() executes DELETE statements correctly", {
  # Clean up any existing test table
  tryCatch(sql_exec("DROP TABLE IF EXISTS test_delete"), error = function(e) NULL)

  sql_exec("CREATE TABLE test_delete (id INTEGER, name VARCHAR)")
  sql_exec("INSERT INTO test_delete VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Charlie')")

  # Test DELETE
  rows_affected <- sql_exec("DELETE FROM test_delete WHERE id = 2")
  expect_equal(rows_affected, 1)

  # Verify the deletion
  result <- sql_query("SELECT COUNT(*) AS count FROM test_delete")
  expect_equal(result$count, 2)

  # Clean up
  sql_exec("DROP TABLE test_delete")
})

test_that("sql_exec() works with custom connection", {
  custom_con <- dbConnect(duckdb())

  rows_affected <- sql_exec("CREATE TABLE test_custom (x INTEGER)", conn = custom_con)
  expect_equal(rows_affected, 0)

  # Verify table exists in custom connection
  result <- sql_query("SELECT name FROM sqlite_master WHERE type='table' AND name='test_custom'", conn = custom_con)
  # Note: DuckDB doesn't have sqlite_master, so let's use a different approach
  tryCatch({
    sql_query("SELECT * FROM test_custom LIMIT 0", conn = custom_con)
    table_exists <- TRUE
  }, error = function(e) {
    table_exists <- FALSE
  })
  expect_true(table_exists)

  dbDisconnect(custom_con, shutdown = TRUE)
})

test_that("sql_exec() validates connection", {
  invalid_con <- dbConnect(duckdb())
  dbDisconnect(invalid_con, shutdown = TRUE)

  expect_snapshot(error = TRUE, sql_exec("SELECT 1", conn = invalid_con))
})

test_that("default_conn() returns a valid connection", {
  conn <- default_conn()
  expect_s4_class(conn, "duckdb_connection")
  expect_true(dbIsValid(conn))
})

test_that("default_conn() returns the same connection on multiple calls", {
  conn1 <- default_conn()
  conn2 <- default_conn()
  expect_identical(conn1, conn2)
})

test_that("default_conn() connection has expected properties", {
  conn <- default_conn()

  # Test that it can execute queries
  result <- dbGetQuery(conn, "SELECT 1 AS test")
  expect_equal(result$test, 1)

  # Test environment_scan is enabled (data frames available)
  test_df_for_scan <- data.frame(x = 1:2)
  result <- dbGetQuery(conn, "SELECT * FROM test_df_for_scan")
  expect_equal(result$x, 1:2)
})

test_that("default_conn() connection uses correct timezone and array settings", {
  conn <- default_conn()

  # Test timezone setting by checking a timestamp query doesn't fail
  # (exact timezone behavior would need more complex testing)
  result <- dbGetQuery(conn, "SELECT CURRENT_TIMESTAMP AS ts")
  expect_s3_class(result, "data.frame")
  expect_equal(ncol(result), 1)

  # Test array setting by creating an array and checking it returns as matrix
  # This is a more complex test that would require specific DuckDB array functions
  # For now, just ensure the connection works with arrays
  tryCatch({
    result <- dbGetQuery(conn, "SELECT [1, 2, 3] AS arr")
    expect_s3_class(result, "data.frame")
  }, error = function(e) {
    # Array syntax might vary, so we'll just check the connection works
    result <- dbGetQuery(conn, "SELECT 1 AS fallback")
    expect_equal(result$fallback, 1)
  })
})

test_that("functions work together in realistic scenarios", {
  # Clean up any existing test data
  tryCatch(sql_exec("DROP TABLE IF EXISTS integration_test"), error = function(e) NULL)

  # Create table
  sql_exec("CREATE TABLE integration_test (id INTEGER, name VARCHAR, score DOUBLE)")

  # Insert data
  rows_inserted <- sql_exec("INSERT INTO integration_test VALUES (1, 'Alice', 95.5), (2, 'Bob', 87.2), (3, 'Charlie', 92.1)")
  expect_equal(rows_inserted, 3)

  # Query data
  result <- sql_query("SELECT * FROM integration_test ORDER BY score DESC")
  expect_equal(nrow(result), 3)
  expect_equal(result$name[1], "Alice")  # Highest score

  # Update data
  updated <- sql_exec("UPDATE integration_test SET score = score + 5 WHERE name = 'Bob'")
  expect_equal(updated, 1)

  # Clean up
  sql_exec("DROP TABLE integration_test")
})

test_that("error handling works correctly", {
  # Test syntax error
  expect_snapshot(error = TRUE, sql_exec("INVALID SQL SYNTAX"))

  # Test that errors don't break subsequent operations
  result <- sql_query("SELECT 'after_error' AS status")
  expect_equal(result$status, "after_error")
})

Try the duckdb package in your browser

Any scripts or data that you put into this service are public.

duckdb documentation built on Nov. 5, 2025, 5:41 p.m.