Skip to content

Commit 6224ef9

Browse files
committed
Add support for DBI::dbBind placeholder parameters
1 parent 3d75aaa commit 6224ef9

5 files changed

Lines changed: 176 additions & 6 deletions

File tree

R/source_sql_to_dataframe.R

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ source_sql_to_dataframe <- function(path, params = NULL) {
1919
comment_close = sqltargets_option_get("sqltargets.jinja_comment_close"),
2020
)
2121
)
22-
} else {
22+
} else if (template_engine == "glue") {
2323
query <- glue::glue_data_sql(
2424
params,
2525
query,
@@ -28,7 +28,17 @@ source_sql_to_dataframe <- function(path, params = NULL) {
2828
.close = sqltargets_option_get("sqltargets.glue_sql_closing_delimiter")
2929
)
3030
}
31-
out <- DBI::dbGetQuery(con, query)
31+
32+
if (template_engine %in% c("jinjar", "glue")) {
33+
out <- DBI::dbGetQuery(con, query)
34+
} else if (template_engine == "dbi") {
35+
sth <- DBI::dbSendQuery(con, query)
36+
DBI::dbBind(sth, params)
37+
out <- DBI::dbFetch(sth, n = -1)
38+
DBI::dbClearResult(sth)
39+
} else {
40+
stop(glue::glue("Unknown template engine: {template_engine}"))
41+
}
3242
msg <- glue::glue("{basename(path)} executed:\n Rows: {nrow(out)}\n Columns: {ncol(out)}")
3343
cli::cli_alert_success(msg)
3444
return(out)

R/sqltargets-option.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#'
1010
#' Available Options
1111
#'
12-
#' - `"sqltargets.template_engine"` - Either 'glue' or 'jinjar'. Determines how the query file should be parsed.
12+
#' - `"sqltargets.template_engine"` - Either 'dbi', 'glue' or 'jinjar'. Determines how the query file should be parsed.
1313
#'
1414
#' - `"sqltargets.glue_sql_opening_delimiter"` - character. Length 1. The opening delimiter passed to `glue::glue_sql()`.
1515
#'

README.Rmd

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ knitr::opts_chunk$set(
2424

2525
<!-- badges: end -->
2626

27-
sqltargets makes it easy to integrate SQL files within your [targets workflows.](https://github.com/ropensci/targets) The shorthand `tar_sql()` creates two targets: (1) the ‘upstream’ SQL file; and (2) the ‘downstream’ result of the query. Dependencies can be specified by calling `tar_load()` within SQL comments. The template engine can be specified using the `sqltargets.template_engine` option (either 'glue' or 'jinjar').
27+
sqltargets makes it easy to integrate SQL files within your [targets workflows.](https://github.com/ropensci/targets) The shorthand `tar_sql()` creates two targets: (1) the ‘upstream’ SQL file; and (2) the ‘downstream’ result of the query. Dependencies can be specified by calling `tar_load()` within SQL comments. The template engine can be specified using the `sqltargets.template_engine` option (either 'dbi', 'glue' or 'jinjar'). 'glue' is the default, 'dbi' is the simplest using [placeholders](https://dbi.r-dbi.org/reference/dbBind.html#details), and 'jinjar' is the most powerful.
2828

2929
## Installation
3030

@@ -93,6 +93,30 @@ list(
9393
)
9494
```
9595
96+
With dbi:
97+
98+
`query.sql`
99+
100+
```sql
101+
-- !preview conn=DBI::dbConnect(RSQLite::SQLite())
102+
-- tar_load(params)
103+
select id
104+
from table
105+
where age > :age_threshold
106+
-- this could be $1, $age_threshold, or ? for SQLite
107+
```
108+
109+
`_targets.R`
110+
111+
```{r eval = FALSE}
112+
library(targets)
113+
library(sqltargets)
114+
list(
115+
tar_target(params, list(age_threshold = 30)),
116+
tar_sql(report, path = "query.sql", params = params)
117+
)
118+
```
119+
96120
With 'Jinja':
97121
98122
`query.sql`

README.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ workflows.](https://github.com/ropensci/targets) The shorthand
2222
`tar_sql()` creates two targets: (1) the ‘upstream’ SQL file; and (2)
2323
the ‘downstream’ result of the query. Dependencies can be specified by
2424
calling `tar_load()` within SQL comments. The template engine can be
25-
specified using the `sqltargets.template_engine` option (either ‘glue’
26-
or ‘jinjar’).
25+
specified using the `sqltargets.template_engine` option (either ‘dbi’,
26+
‘glue’ or ‘jinjar’). ‘glue’ is the default, ‘dbi’ is the simplest using
27+
[placeholders](https://dbi.r-dbi.org/reference/dbBind.html#details), and
28+
‘jinjar’ is the most powerful.
2729

2830
## Installation
2931

@@ -100,6 +102,30 @@ list(
100102
)
101103
```
102104
105+
With dbi:
106+
107+
`query.sql`
108+
109+
``` sql
110+
-- !preview conn=DBI::dbConnect(RSQLite::SQLite())
111+
-- tar_load(params)
112+
select id
113+
from table
114+
where age > :age_threshold
115+
-- this could be $1, $age_threshold, or ? for SQLite
116+
```
117+
118+
`_targets.R`
119+
120+
``` r
121+
library(targets)
122+
library(sqltargets)
123+
list(
124+
tar_target(params, list(age_threshold = 30)),
125+
tar_sql(report, path = "query.sql", params = params)
126+
)
127+
```
128+
103129
With ‘Jinja’:
104130
105131
`query.sql`

tests/testthat/test-tar-sql.R

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,113 @@ targets::tar_test("tar_sql() for Jinja for loop", {
115115
credit_card_amount = 0,
116116
gift_card_amount = 0))
117117
})
118+
119+
targets::tar_test("tar_sql() with positional dbBind DBI engine", {
120+
db_file <- tempfile(pattern = "sqlite", fileext = ".db")
121+
lines <- c(
122+
glue::glue("-- !preview conn=DBI::dbConnect(RSQLite::SQLite(), dbname = \"{db_file}\")"),
123+
"-- tar_load(create_iris_table)",
124+
"select distinct(species) as wide_petals from iris where [Petal.Width] > ?",
125+
""
126+
)
127+
writeLines(lines, "query.sql")
128+
targets::tar_script({
129+
library(sqltargets)
130+
sqltargets_option_set("sqltargets.template_engine", "dbi")
131+
list(
132+
tar_target(test_db_file, db_file),
133+
tar_target(create_iris_table, {
134+
conn <- DBI::dbConnect(RSQLite::SQLite(), dbname = test_db_file)
135+
on.exit(DBI::dbDisconnect(conn), after = TRUE)
136+
DBI::dbWriteTable(conn, 'iris', iris)
137+
TRUE
138+
}),
139+
tar_sql(
140+
report,
141+
path = "query.sql",
142+
params = list(2.3)
143+
)
144+
)
145+
})
146+
suppressMessages(targets::tar_make(callr_function = NULL))
147+
out <- targets::tar_read(report)
148+
expect_equal(out, data.frame(wide_petals = "virginica"))
149+
})
150+
151+
targets::tar_test("tar_sql() with named dbBind DBI engine", {
152+
db_file <- tempfile(pattern = "sqlite", fileext = ".db")
153+
lines <- c(
154+
glue::glue("-- !preview conn=DBI::dbConnect(RSQLite::SQLite(), dbname = \"{db_file}\")"),
155+
"-- tar_load(create_iris_table)",
156+
"select distinct(species) as short_petals from iris where [Petal.Length] < $petal_length",
157+
""
158+
)
159+
writeLines(lines, "named_ph_dollar.sql")
160+
lines <- c(
161+
glue::glue("-- !preview conn=DBI::dbConnect(RSQLite::SQLite(), dbname = \"{db_file}\")"),
162+
"-- tar_load(create_iris_table)",
163+
"select distinct(species) as short_petals from iris where [Petal.Length] < :petal_length",
164+
""
165+
)
166+
writeLines(lines, "named_ph_colon.sql")
167+
targets::tar_script({
168+
library(sqltargets)
169+
sqltargets_option_set("sqltargets.template_engine", "dbi")
170+
list(
171+
tar_target(test_db_file, db_file),
172+
tar_target(create_iris_table, {
173+
conn <- DBI::dbConnect(RSQLite::SQLite(), dbname = test_db_file)
174+
on.exit(DBI::dbDisconnect(conn), after = TRUE)
175+
DBI::dbWriteTable(conn, 'iris', iris)
176+
TRUE
177+
}),
178+
tar_sql(
179+
report,
180+
path = "named_ph_dollar.sql",
181+
params = list(petal_length=1.5)
182+
),
183+
tar_sql(
184+
report2,
185+
path = "named_ph_colon.sql",
186+
params = data.frame(petal_length=1.5)
187+
)
188+
)
189+
})
190+
suppressMessages(targets::tar_make(callr_function = NULL))
191+
out <- targets::tar_read(report)
192+
expect_equal(out, data.frame(short_petals = "setosa"))
193+
out2 <- targets::tar_read(report2)
194+
expect_equal(out2, data.frame(short_petals = "setosa"))
195+
})
196+
197+
targets::tar_test("tar_sql() with indexed dbBind DBI engine", {
198+
db_file <- tempfile(pattern = "sqlite", fileext = ".db")
199+
lines <- c(
200+
glue::glue("-- !preview conn=DBI::dbConnect(RSQLite::SQLite(), dbname = \"{db_file}\")"),
201+
"-- tar_load(create_iris_table)",
202+
"select species from iris where [Sepal.Length] > $1 and [Petal.Width] < $2 and [Sepal.Width] < $1",
203+
""
204+
)
205+
writeLines(lines, "query.sql")
206+
targets::tar_script({
207+
library(sqltargets)
208+
sqltargets_option_set("sqltargets.template_engine", "dbi")
209+
list(
210+
tar_target(test_db_file, db_file),
211+
tar_target(create_iris_table, {
212+
conn <- DBI::dbConnect(RSQLite::SQLite(), dbname = test_db_file)
213+
on.exit(DBI::dbDisconnect(conn), after = TRUE)
214+
DBI::dbWriteTable(conn, 'iris', iris)
215+
TRUE
216+
}),
217+
tar_sql(
218+
report,
219+
path = "query.sql",
220+
params = list(5.0, 1.0)
221+
)
222+
)
223+
})
224+
suppressMessages(targets::tar_make(callr_function = NULL))
225+
out <- targets::tar_read(report)
226+
expect_equal(out, data.frame(Species = rep("setosa", 22)))
227+
})

0 commit comments

Comments
 (0)