Skip to content

Commit 93dcaf8

Browse files
authored
fix(pkg-r): querychat_app() should only close connections if it creates the DB (#164)
* fix(pkg-r): `querychat_app()` only closes connections if it creates the DB * chore(pkg-r) Refactor for clarity * chore: Add NEWS * chore(pkg-r): Actually, only if a data frame * tests(pkg-r): Test that cleanup is correctly set in querychat_app() * feat: automatic cleanup QC when created in running Shiny app * feat(querychat_app): Throw if called inside a Shiny app * feat(querychat_app): Always clean up dataframe dbs * chore: move table validation into `normalize_data_source()` * chore(querychat_app): Resolve `cleanup` early
1 parent 87d9650 commit 93dcaf8

File tree

6 files changed

+78
-11
lines changed

6 files changed

+78
-11
lines changed

pkg-r/NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# querychat (development version)
22

3+
* `querychat_app()` will now only automatically clean up the data source if QueryChat creates the data source internally from a data frame. (#164)
4+
35
* **Breaking change:** The `$sql()` method now returns `NULL` instead of `""` (empty string) when no query has been set, aligning with the behavior of `$title()` for consistency. Most code using `isTruthy()` or similar falsy checks will continue working without changes. Code that explicitly checks `sql() == ""` should be updated to use falsy checks (e.g., `!isTruthy(sql())`) or explicit null checks (`is.null(sql())`). (#146)
46

57
* Tool detail cards can now be expanded or collapsed by default when querychat runs a query or updates the dashboard via the `querychat.tool_details` R option or the `QUERYCHAT_TOOL_DETAILS` environment variable. Valid values are `"expanded"`, `"collapsed"`, or `"default"`. (#137)

pkg-r/R/QueryChat.R

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,6 @@ QueryChat <- R6::R6Class(
166166

167167
private$.data_source <- normalize_data_source(data_source, table_name)
168168

169-
# Validate table name
170-
check_sql_table_name(table_name)
171-
172169
self$id <- id %||% table_name
173170

174171
if (!is.null(greeting) && file.exists(greeting)) {
@@ -192,7 +189,7 @@ QueryChat <- R6::R6Class(
192189

193190
# By default, only close automatically if a Shiny session is active
194191
if (is.na(cleanup)) {
195-
cleanup <- !is.null(shiny::getDefaultReactiveDomain())
192+
cleanup <- shiny::isRunning()
196193
}
197194

198195
if (cleanup) {
@@ -585,8 +582,11 @@ QueryChat <- R6::R6Class(
585582
#' used. See the package prompts directory for the default template format.
586583
#' @param cleanup Whether or not to automatically run `$cleanup()` when the
587584
#' Shiny session/app stops. By default, cleanup only occurs if `QueryChat`
588-
#' gets created within a Shiny session. Set to `TRUE` to always clean up,
589-
#' or `FALSE` to never clean up automatically.
585+
#' is created within a Shiny app. Set to `TRUE` to always clean up, or
586+
#' `FALSE` to never clean up automatically.
587+
#'
588+
#' In `querychat_app()`, in-memory databases created for data frames are
589+
#' always cleaned up.
590590
#'
591591
#' @return A `QueryChat` object. See [QueryChat] for available methods.
592592
#'
@@ -668,13 +668,26 @@ querychat_app <- function(
668668
categorical_threshold = 20,
669669
extra_instructions = NULL,
670670
prompt_template = NULL,
671-
cleanup = TRUE,
671+
cleanup = NA,
672672
bookmark_store = "url"
673673
) {
674+
if (shiny::isRunning()) {
675+
cli::cli_abort(
676+
"{.fn querychat_app} cannot be called from within a Shiny app. Use {.fn querychat} instead."
677+
)
678+
}
679+
674680
if (is_missing(table_name) && is.data.frame(data_source)) {
675681
table_name <- deparse1(substitute(data_source))
676682
}
677683

684+
check_bool(cleanup, allow_na = TRUE)
685+
if (is.data.frame(data_source)) {
686+
cleanup <- TRUE
687+
} else if (is.na(cleanup)) {
688+
cleanup <- FALSE
689+
}
690+
678691
qc <- QueryChat$new(
679692
data_source = data_source,
680693
table_name = table_name,
@@ -697,6 +710,8 @@ normalize_data_source <- function(data_source, table_name) {
697710
return(data_source)
698711
}
699712

713+
check_sql_table_name(table_name, call = caller_env())
714+
700715
if (is.data.frame(data_source)) {
701716
return(DataFrameSource$new(data_source, table_name))
702717
}

pkg-r/R/utils-shiny.R

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
in_shiny_session <- function() {
2+
!is.null(shiny::getDefaultReactiveDomain()) # nocov
3+
}

pkg-r/man/querychat-convenience.Rd

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

pkg-r/man/querychat-package.Rd

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,3 +306,39 @@ describe("normalize_data_source()", {
306306
})
307307
})
308308
})
309+
310+
test_that("querychat_app() only cleans up data frame sources on exit", {
311+
local_mocked_r6_class(
312+
QueryChat,
313+
public = list(
314+
initialize = function(..., cleanup) {
315+
# have to use an option because the code is evaluated in a far-away env
316+
options(.test_cleanup = cleanup)
317+
},
318+
app = function(...) {}
319+
)
320+
)
321+
withr::local_options(rlang_interactive = TRUE)
322+
323+
withr::with_options(list(.test_cleanup = NULL), {
324+
test_df <- new_test_df()
325+
querychat_app(test_df)
326+
cleanup_result <- getOption(".test_cleanup")
327+
expect_true(cleanup_result)
328+
})
329+
330+
withr::with_options(list(.test_cleanup = NULL), {
331+
test_ds <- local_data_frame_source(new_test_df())
332+
querychat_app(test_ds)
333+
cleanup_result <- getOption(".test_cleanup")
334+
expect_false(cleanup_result)
335+
})
336+
337+
withr::with_options(list(.test_cleanup = NULL), {
338+
con <- local_sqlite_connection(new_test_df())
339+
test_ds <- DBISource$new(con$conn, "test_table")
340+
querychat_app(test_ds)
341+
cleanup_result <- getOption(".test_cleanup")
342+
expect_false(cleanup_result)
343+
})
344+
})

0 commit comments

Comments
 (0)