Skip to content

Commit a7896cf

Browse files
committed
feat: Use duckdb or sqlite based on first available
1 parent 6f96db5 commit a7896cf

File tree

8 files changed

+95
-15
lines changed

8 files changed

+95
-15
lines changed

pkg-r/R/DataSource.R

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,24 +98,30 @@ DataSource <- R6::R6Class(
9898
#' database table. See [DBISource] for the full description of available
9999
#' methods.
100100
#'
101-
#' Use the `engine` parameter to choose between "duckdb" (default) or "sqlite",
102-
#' or set the global option `querychat.DataFrameSource.engine to choose the
103-
#' default engine for all DataFrameSource instances.
101+
#' By default, DataFrameSource uses the first available engine from duckdb
102+
#' (checked first) or RSQLite. You can explicitly set the `engine` parameter to
103+
#' choose between "duckdb" or "sqlite", or set the global option
104+
#' `querychat.DataFrameSource.engine` to choose the default engine for all
105+
#' DataFrameSource instances. At least one of these packages must be installed.
104106
#'
105107
#' @export
106108
#' @examples
107109
#' \dontrun{
108-
#' # Create a data frame source
110+
#' # Create a data frame source (uses first available: duckdb or sqlite)
109111
#' df_source <- DataFrameSource$new(mtcars, "mtcars")
110112
#'
111113
#' # Get database type
112-
#' df_source$get_db_type() # Returns "DuckDB"
114+
#' df_source$get_db_type() # Returns "DuckDB" or "SQLite"
113115
#'
114116
#' # Execute a query
115117
#' result <- df_source$execute_query("SELECT * FROM mtcars WHERE mpg > 25")
116118
#'
119+
#' # Explicitly choose an engine
120+
#' df_sqlite <- DataFrameSource$new(mtcars, "mtcars", engine = "sqlite")
121+
#'
117122
#' # Clean up when done
118123
#' df_source$cleanup()
124+
#' df_sqlite$cleanup()
119125
#' }
120126
DataFrameSource <- R6::R6Class(
121127
"DataFrameSource",
@@ -131,6 +137,10 @@ DataFrameSource <- R6::R6Class(
131137
#' @param table_name Name to use for the table in SQL queries. Must be a
132138
#' valid table name (start with letter, contain only letters, numbers,
133139
#' and underscores)
140+
#' @param engine Database engine to use: "duckdb" or "sqlite". Set the
141+
#' global option `querychat.DataFrameSource.engine` to specify the default
142+
#' engine for all instances. If NULL (default), uses the first available
143+
#' engine from duckdb or RSQLite (in that order).
134144
#' @return A new DataFrameSource object
135145
#' @examples
136146
#' \dontrun{
@@ -139,11 +149,12 @@ DataFrameSource <- R6::R6Class(
139149
initialize = function(
140150
df,
141151
table_name,
142-
engine = getOption("querychat.DataFrameSource.engine", "duckdb")
152+
engine = getOption("querychat.DataFrameSource.engine", NULL)
143153
) {
144154
check_data_frame(df)
145155
check_sql_table_name(table_name)
146156

157+
engine <- engine %||% get_default_dataframe_engine()
147158
engine <- tolower(engine)
148159
arg_match(engine, c("duckdb", "sqlite"))
149160

@@ -341,6 +352,22 @@ is_data_source <- function(x) {
341352
}
342353

343354

355+
get_default_dataframe_engine <- function() {
356+
if (is_installed("duckdb")) {
357+
return("duckdb")
358+
}
359+
if (is_installed("RSQLite")) {
360+
return("sqlite")
361+
}
362+
cli::cli_abort(c(
363+
"No compatible database engine installed for DataFrameSource",
364+
"i" = "Install either {.pkg duckdb} or {.pkg RSQLite}:",
365+
" " = "{.run install.packages(\"duckdb\")}",
366+
" " = "{.run install.packages(\"RSQLite\")}"
367+
))
368+
}
369+
370+
344371
get_schema_impl <- function(conn, table_name, categorical_threshold = 20) {
345372
# Get column information
346373
columns <- DBI::dbListFields(conn, table_name)

pkg-r/R/querychat-package.R

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,5 @@ release_bullets <- function() {
8484
}
8585

8686
suppress_rcmdcheck <- function() {
87-
duckdb::duckdb
8887
S7::S7_class
8988
}

pkg-r/man/DataFrameSource.Rd

Lines changed: 17 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg-r/tests/testthat/helper-fixtures.R

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ local_sqlite_connection <- function(
7171
list(conn = conn, path = temp_db)
7272
}
7373

74+
# Skip test if no DataFrameSource engine is available
75+
skip_if_no_dataframe_engine <- function() {
76+
if (!rlang::is_installed("duckdb") && !rlang::is_installed("RSQLite")) {
77+
skip("Neither duckdb nor RSQLite is installed")
78+
}
79+
}
80+
7481
# Create a DataFrameSource with automatic cleanup
7582
local_data_frame_source <- function(
7683
data,

pkg-r/tests/testthat/test-DataSource.R

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ describe("DataSource base class", {
3030
})
3131

3232
describe("DataFrameSource$new()", {
33+
skip_if_no_dataframe_engine()
34+
3335
it("creates proper R6 object for DataFrameSource", {
3436
test_df <- new_test_df()
3537

@@ -268,6 +270,8 @@ describe("DBISource$new()", {
268270

269271
describe("DataSource$get_schema()", {
270272
it("returns proper schema for DataFrameSource", {
273+
skip_if_no_dataframe_engine()
274+
271275
df_source <- local_data_frame_source(new_mixed_types_df())
272276

273277
schema <- df_source$get_schema()
@@ -295,6 +299,8 @@ describe("DataSource$get_schema()", {
295299
})
296300

297301
it("correctly reports min/max values for numeric columns", {
302+
skip_if_no_dataframe_engine()
303+
298304
df_source <- local_data_frame_source(new_metrics_df())
299305

300306
schema <- df_source$get_schema()
@@ -306,9 +312,12 @@ describe("DataSource$get_schema()", {
306312
})
307313

308314
describe("DataSource$get_db_type()", {
309-
it("returns DuckDB for DataFrameSource", {
315+
it("returns correct database type for DataFrameSource", {
316+
skip_if_no_dataframe_engine()
317+
310318
df_source <- local_data_frame_source(new_test_df())
311-
expect_equal(df_source$get_db_type(), "DuckDB")
319+
db_type <- df_source$get_db_type()
320+
expect_true(db_type %in% c("DuckDB", "SQLite"))
312321
})
313322

314323
it("returns correct type for SQLite connections", {
@@ -325,6 +334,9 @@ describe("DataSource$get_data()", {
325334
test_df <- new_test_df()
326335

327336
it("returns all data for both DataFrameSource and DBISource", {
337+
skip_if_no_dataframe_engine()
338+
skip_if_not_installed("RSQLite")
339+
328340
df_source <- local_data_frame_source(test_df)
329341

330342
result <- df_source$get_data()
@@ -343,6 +355,9 @@ describe("DataSource$get_data()", {
343355
})
344356

345357
describe("DataSource$execute_query()", {
358+
skip_if_no_dataframe_engine()
359+
skip_if_not_installed("RSQLite")
360+
346361
test_df <- new_test_df(rows = 4)
347362
df_source <- local_data_frame_source(test_df)
348363
db <- local_sqlite_connection(test_df)
@@ -589,6 +604,8 @@ describe("DBISource$test_query()", {
589604
})
590605

591606
describe("DataFrameSource$test_query()", {
607+
skip_if_no_dataframe_engine()
608+
592609
test_df <- new_users_df()
593610
df_source <- local_data_frame_source(test_df, "test_table")
594611

pkg-r/tests/testthat/test-QueryChat.R

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
describe("QueryChat$new()", {
2+
skip_if_no_dataframe_engine()
3+
24
it("automatically converts data.frame to DataFrameSource", {
35
qc <- QueryChat$new(
46
data_source = new_test_df(),
@@ -231,6 +233,8 @@ describe("QueryChat$system_prompt", {
231233
})
232234

233235
describe("QueryChat$data_source", {
236+
skip_if_no_dataframe_engine()
237+
234238
it("returns the data source object", {
235239
test_df <- new_test_df()
236240
qc <- QueryChat$new(test_df, greeting = "Test")
@@ -470,6 +474,7 @@ test_that("QueryChat$server() errors when called outside Shiny context", {
470474
})
471475

472476
describe("querychat()", {
477+
skip_if_no_dataframe_engine()
473478
withr::local_envvar(OPENAI_API_KEY = "boop")
474479

475480
it("creates a QueryChat object", {
@@ -597,6 +602,8 @@ describe("QueryChat$console()", {
597602
})
598603

599604
describe("normalize_data_source()", {
605+
skip_if_no_dataframe_engine()
606+
600607
it("returns DataSource objects unchanged", {
601608
test_df <- new_test_df()
602609
df_source <- DataFrameSource$new(test_df, "test_df")

pkg-r/tests/testthat/test-QueryChatSystemPrompt.R

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
describe("QueryChatSystemPrompt$new()", {
2+
skip_if_no_dataframe_engine()
3+
24
it("initializes with string template", {
35
df <- new_test_df()
46
ds <- DataFrameSource$new(df, "test_table")

pkg-r/tests/testthat/test-querychat_tools.R

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
test_that("tool_update_dashboard() checks inputs", {
2+
skip_if_no_dataframe_engine()
3+
24
expect_snapshot(error = TRUE, tool_update_dashboard("foo"))
35

46
df_source <- local_data_frame_source(new_test_df())
@@ -88,6 +90,8 @@ describe("querychat_tool_details_option()", {
8890
})
8991

9092
describe("querychat_tool_result()", {
93+
skip_if_no_dataframe_engine()
94+
9195
it("returns successful result for valid query action", {
9296
df_source <- local_data_frame_source(new_test_df())
9397

@@ -283,6 +287,8 @@ describe("querychat_tool_result()", {
283287
})
284288

285289
describe("tool_query()", {
290+
skip_if_no_dataframe_engine()
291+
286292
it("returns an ellmer tool object", {
287293
df_source <- local_data_frame_source(new_test_df())
288294
tool <- tool_query(df_source)
@@ -312,6 +318,8 @@ describe("tool_query()", {
312318
})
313319

314320
describe("tool_update_dashboard()", {
321+
skip_if_no_dataframe_engine()
322+
315323
it("returns an ellmer tool object", {
316324
df_source <- local_data_frame_source(new_test_df())
317325

@@ -370,6 +378,8 @@ describe("tool_reset_dashboard()", {
370378
})
371379

372380
describe("tool_update_dashboard_impl()", {
381+
skip_if_no_dataframe_engine()
382+
373383
it("returns a function", {
374384
df_source <- local_data_frame_source(new_test_df())
375385
current_query <- shiny::reactiveVal("SELECT * FROM test_table")

0 commit comments

Comments
 (0)