dbplyr/0000755000176200001440000000000014033054373011550 5ustar liggesusersdbplyr/NAMESPACE0000644000176200001440000003556714032677513013015 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method(anti_join,tbl_lazy) S3method(arrange,tbl_lazy) S3method(as.data.frame,tbl_lazy) S3method(as.data.frame,tbl_sql) S3method(as.sql,Id) S3method(as.sql,character) S3method(as.sql,dbplyr_schema) S3method(as.sql,ident) S3method(as.sql,sql) S3method(auto_copy,tbl_sql) S3method(c,ident) S3method(c,sql) S3method(collapse,tbl_sql) S3method(collect,tbl_sql) S3method(compute,tbl_sql) S3method(copy_to,src_sql) S3method(count,tbl_lazy) S3method(db_analyze,DBIConnection) S3method(db_collect,DBIConnection) S3method(db_compute,DBIConnection) S3method(db_connection_describe,DBIConnection) S3method(db_connection_describe,MariaDBConnection) S3method(db_connection_describe,MySQL) S3method(db_connection_describe,MySQLConnection) S3method(db_connection_describe,OdbcConnection) S3method(db_connection_describe,PostgreSQL) S3method(db_connection_describe,PostgreSQLConnection) S3method(db_connection_describe,PqConnection) S3method(db_connection_describe,SQLiteConnection) S3method(db_copy_to,DBIConnection) S3method(db_create_index,DBIConnection) S3method(db_desc,DBIConnection) S3method(db_explain,DBIConnection) S3method(db_query_fields,DBIConnection) S3method(db_query_fields,PostgreSQLConnection) S3method(db_save_query,DBIConnection) S3method(db_sql_render,DBIConnection) S3method(db_table_temporary,"Microsoft SQL Server") S3method(db_table_temporary,DBIConnection) S3method(db_table_temporary,HDB) S3method(db_write_table,DBIConnection) S3method(db_write_table,PostgreSQLConnection) S3method(dbi_quote,ident_q) S3method(dbplyr_edition,"Microsoft SQL Server") S3method(dbplyr_edition,ACCESS) S3method(dbplyr_edition,HDB) S3method(dbplyr_edition,Hive) S3method(dbplyr_edition,Impala) S3method(dbplyr_edition,MariaDBConnection) S3method(dbplyr_edition,MySQL) S3method(dbplyr_edition,MySQLConnection) S3method(dbplyr_edition,OdbcConnection) S3method(dbplyr_edition,OraConnection) S3method(dbplyr_edition,Oracle) S3method(dbplyr_edition,PostgreSQL) S3method(dbplyr_edition,PostgreSQLConnection) S3method(dbplyr_edition,PqConnection) S3method(dbplyr_edition,Redshift) S3method(dbplyr_edition,RedshiftConnection) S3method(dbplyr_edition,SQLiteConnection) S3method(dbplyr_edition,Teradata) S3method(dbplyr_edition,TestConnection) S3method(dbplyr_edition,default) S3method(dbplyr_fill0,ACCESS) S3method(dbplyr_fill0,DBIConnection) S3method(dbplyr_fill0,HDB) S3method(dbplyr_fill0,MariaDBConnection) S3method(dbplyr_fill0,MySQL) S3method(dbplyr_fill0,MySQLConnection) S3method(dbplyr_fill0,PostgreSQL) S3method(dbplyr_fill0,PqConnection) S3method(dbplyr_fill0,SQLiteConnection) S3method(dim,tbl_lazy) S3method(dimnames,tbl_lazy) S3method(distinct,tbl_lazy) S3method(do,tbl_sql) S3method(escape,"NULL") S3method(escape,Date) S3method(escape,POSIXt) S3method(escape,blob) S3method(escape,character) S3method(escape,data.frame) S3method(escape,double) S3method(escape,factor) S3method(escape,ident) S3method(escape,ident_q) S3method(escape,integer) S3method(escape,integer64) S3method(escape,list) S3method(escape,logical) S3method(escape,reactivevalues) S3method(escape,sql) S3method(explain,tbl_sql) S3method(format,ident) S3method(format,sql) S3method(format,src_sql) S3method(full_join,tbl_lazy) S3method(group_by,tbl_lazy) S3method(group_size,tbl_sql) S3method(group_vars,tbl_lazy) S3method(groups,tbl_lazy) S3method(head,tbl_lazy) S3method(inner_join,tbl_lazy) S3method(last_value_sql,DBIConnection) S3method(last_value_sql,Hive) S3method(left_join,tbl_lazy) S3method(mutate,tbl_lazy) S3method(n_groups,tbl_sql) S3method(names,sql_variant) S3method(op_desc,op) S3method(op_desc,op_arrange) S3method(op_desc,op_base_remote) S3method(op_desc,op_group_by) S3method(op_frame,op_base) S3method(op_frame,op_double) S3method(op_frame,op_frame) S3method(op_frame,op_single) S3method(op_frame,tbl_lazy) S3method(op_grps,op_base) S3method(op_grps,op_double) S3method(op_grps,op_group_by) S3method(op_grps,op_select) S3method(op_grps,op_single) S3method(op_grps,op_summarise) S3method(op_grps,op_ungroup) S3method(op_grps,tbl_lazy) S3method(op_sort,op_arrange) S3method(op_sort,op_base) S3method(op_sort,op_double) S3method(op_sort,op_order) S3method(op_sort,op_single) S3method(op_sort,op_summarise) S3method(op_sort,tbl_lazy) S3method(op_vars,op_base) S3method(op_vars,op_distinct) S3method(op_vars,op_double) S3method(op_vars,op_join) S3method(op_vars,op_select) S3method(op_vars,op_semi_join) S3method(op_vars,op_set_op) S3method(op_vars,op_single) S3method(op_vars,op_summarise) S3method(op_vars,tbl_lazy) S3method(print,dbplyr_schema) S3method(print,ident) S3method(print,join_query) S3method(print,op_base_local) S3method(print,op_base_remote) S3method(print,op_single) S3method(print,select_query) S3method(print,semi_join_query) S3method(print,set_op_query) S3method(print,sql) S3method(print,sql_variant) S3method(print,tbl_lazy) S3method(print,tbl_sql) S3method(pull,tbl_sql) S3method(relocate,tbl_lazy) S3method(rename,tbl_lazy) S3method(rename_with,tbl_lazy) S3method(right_join,tbl_lazy) S3method(same_src,src_sql) S3method(same_src,tbl_lazy) S3method(same_src,tbl_sql) S3method(select,tbl_lazy) S3method(semi_join,tbl_lazy) S3method(show_query,tbl_lazy) S3method(slice,tbl_lazy) S3method(slice_head,tbl_lazy) S3method(slice_max,tbl_lazy) S3method(slice_min,tbl_lazy) S3method(slice_sample,tbl_lazy) S3method(slice_tail,tbl_lazy) S3method(sql_build,ident) S3method(sql_build,op_arrange) S3method(sql_build,op_base_local) S3method(sql_build,op_base_remote) S3method(sql_build,op_distinct) S3method(sql_build,op_filter) S3method(sql_build,op_frame) S3method(sql_build,op_group_by) S3method(sql_build,op_head) S3method(sql_build,op_join) S3method(sql_build,op_order) S3method(sql_build,op_select) S3method(sql_build,op_semi_join) S3method(sql_build,op_set_op) S3method(sql_build,op_summarise) S3method(sql_build,op_ungroup) S3method(sql_build,tbl_lazy) S3method(sql_escape_date,ACCESS) S3method(sql_escape_date,DBIConnection) S3method(sql_escape_datetime,ACCESS) S3method(sql_escape_datetime,DBIConnection) S3method(sql_escape_ident,DBIConnection) S3method(sql_escape_ident,TestConnection) S3method(sql_escape_logical,"Microsoft SQL Server") S3method(sql_escape_logical,ACCESS) S3method(sql_escape_logical,DBIConnection) S3method(sql_escape_logical,SQLiteConnection) S3method(sql_escape_raw,"Microsoft SQL Server") S3method(sql_escape_raw,DBIConnection) S3method(sql_escape_string,DBIConnection) S3method(sql_escape_string,TestConnection) S3method(sql_expr_matches,DBIConnection) S3method(sql_expr_matches,MariaDBConnection) S3method(sql_expr_matches,MySQL) S3method(sql_expr_matches,MySQLConnection) S3method(sql_expr_matches,OraConnection) S3method(sql_expr_matches,Oracle) S3method(sql_expr_matches,PostgreSQL) S3method(sql_expr_matches,PostgreSQLConnection) S3method(sql_expr_matches,PqConnection) S3method(sql_expr_matches,SQLiteConnection) S3method(sql_join,DBIConnection) S3method(sql_join_suffix,DBIConnection) S3method(sql_optimise,ident) S3method(sql_optimise,query) S3method(sql_optimise,select_query) S3method(sql_optimise,sql) S3method(sql_query_explain,DBIConnection) S3method(sql_query_explain,Oracle) S3method(sql_query_explain,PostgreSQL) S3method(sql_query_explain,PostgreSQLConnection) S3method(sql_query_explain,PqConnection) S3method(sql_query_explain,SQLiteConnection) S3method(sql_query_fields,DBIConnection) S3method(sql_query_join,DBIConnection) S3method(sql_query_join,MariaDBConnection) S3method(sql_query_join,MySQL) S3method(sql_query_join,MySQLConnection) S3method(sql_query_join,SQLiteConnection) S3method(sql_query_rows,DBIConnection) S3method(sql_query_save,"Microsoft SQL Server") S3method(sql_query_save,DBIConnection) S3method(sql_query_select,"Microsoft SQL Server") S3method(sql_query_select,ACCESS) S3method(sql_query_select,DBIConnection) S3method(sql_query_select,OraConnection) S3method(sql_query_select,Oracle) S3method(sql_query_select,Teradata) S3method(sql_query_semi_join,DBIConnection) S3method(sql_query_set_op,DBIConnection) S3method(sql_query_set_op,SQLiteConnection) S3method(sql_query_wrap,DBIConnection) S3method(sql_query_wrap,OraConnection) S3method(sql_query_wrap,Oracle) S3method(sql_query_wrap,SQLiteConnection) S3method(sql_render,ident) S3method(sql_render,join_query) S3method(sql_render,op) S3method(sql_render,select_query) S3method(sql_render,semi_join_query) S3method(sql_render,set_op_query) S3method(sql_render,sql) S3method(sql_render,tbl_lazy) S3method(sql_select,DBIConnection) S3method(sql_semi_join,DBIConnection) S3method(sql_set_op,DBIConnection) S3method(sql_subquery,DBIConnection) S3method(sql_table_analyze,"Microsoft SQL Server") S3method(sql_table_analyze,ACCESS) S3method(sql_table_analyze,DBIConnection) S3method(sql_table_analyze,HDB) S3method(sql_table_analyze,Hive) S3method(sql_table_analyze,Impala) S3method(sql_table_analyze,MariaDBConnection) S3method(sql_table_analyze,MySQL) S3method(sql_table_analyze,MySQLConnection) S3method(sql_table_analyze,OraConnection) S3method(sql_table_analyze,Oracle) S3method(sql_table_analyze,Snowflake) S3method(sql_table_analyze,Teradata) S3method(sql_table_index,DBIConnection) S3method(sql_translate_env,DBIConnection) S3method(sql_translation,"Microsoft SQL Server") S3method(sql_translation,ACCESS) S3method(sql_translation,DBIConnection) S3method(sql_translation,HDB) S3method(sql_translation,Hive) S3method(sql_translation,Impala) S3method(sql_translation,MariaDBConnection) S3method(sql_translation,MySQL) S3method(sql_translation,MySQLConnection) S3method(sql_translation,OdbcConnection) S3method(sql_translation,OraConnection) S3method(sql_translation,Oracle) S3method(sql_translation,PostgreSQL) S3method(sql_translation,PostgreSQLConnection) S3method(sql_translation,PqConnection) S3method(sql_translation,Redshift) S3method(sql_translation,RedshiftConnection) S3method(sql_translation,SQLiteConnection) S3method(sql_translation,Snowflake) S3method(sql_translation,Teradata) S3method(src_tbls,src_sql) S3method(summarise,tbl_lazy) S3method(tail,tbl_lazy) S3method(tally,tbl_lazy) S3method(tbl,src_dbi) S3method(tbl_sum,tbl_sql) S3method(tbl_vars,tbl_lazy) S3method(transmute,tbl_lazy) S3method(ungroup,tbl_lazy) S3method(union_all,tbl_lazy) S3method(unique,sql) export("%>%") export(add_op_single) export(as.sql) export(base_agg) export(base_no_win) export(base_odbc_agg) export(base_odbc_scalar) export(base_odbc_win) export(base_scalar) export(base_win) export(build_sql) export(copy_lahman) export(copy_nycflights13) export(db_collect) export(db_compute) export(db_connection_describe) export(db_copy_to) export(db_sql_render) export(db_table_temporary) export(dbplyr_edition) export(dbplyr_uncount) export(escape) export(escape_ansi) export(has_lahman) export(has_nycflights13) export(ident) export(ident_q) export(in_schema) export(is.ident) export(is.sql) export(join_query) export(lahman_mysql) export(lahman_postgres) export(lahman_sqlite) export(lahman_srcs) export(lazy_frame) export(memdb_frame) export(named_commas) export(nycflights13_postgres) export(nycflights13_sqlite) export(op_base) export(op_double) export(op_frame) export(op_grps) export(op_single) export(op_sort) export(op_vars) export(partial_eval) export(remote_con) export(remote_name) export(remote_query) export(remote_query_plan) export(remote_src) export(select_query) export(semi_join_query) export(set_op_query) export(simulate_access) export(simulate_dbi) export(simulate_hana) export(simulate_hive) export(simulate_impala) export(simulate_mssql) export(simulate_mysql) export(simulate_odbc) export(simulate_oracle) export(simulate_postgres) export(simulate_redshift) export(simulate_snowflake) export(simulate_sqlite) export(simulate_teradata) export(sql) export(sql_aggregate) export(sql_aggregate_2) export(sql_aggregate_n) export(sql_build) export(sql_call2) export(sql_cast) export(sql_cot) export(sql_escape_date) export(sql_escape_datetime) export(sql_escape_logical) export(sql_escape_raw) export(sql_expr) export(sql_expr_matches) export(sql_infix) export(sql_join_suffix) export(sql_log) export(sql_not_supported) export(sql_optimise) export(sql_paste) export(sql_paste_infix) export(sql_prefix) export(sql_query_explain) export(sql_query_fields) export(sql_query_join) export(sql_query_rows) export(sql_query_save) export(sql_query_select) export(sql_query_semi_join) export(sql_query_set_op) export(sql_query_wrap) export(sql_quote) export(sql_render) export(sql_str_sub) export(sql_substr) export(sql_table_analyze) export(sql_table_index) export(sql_translation) export(sql_translator) export(sql_try_cast) export(sql_variant) export(sql_vector) export(src_dbi) export(src_memdb) export(src_sql) export(src_test) export(tbl_lazy) export(tbl_memdb) export(tbl_sql) export(test_frame) export(test_load) export(test_register_con) export(test_register_src) export(translate_sql) export(translate_sql_) export(win_absent) export(win_aggregate) export(win_aggregate_2) export(win_cumulative) export(win_current_frame) export(win_current_group) export(win_current_order) export(win_over) export(win_rank) export(win_recycled) export(window_frame) export(window_order) import(DBI) import(rlang) importFrom(R6,R6Class) importFrom(assertthat,assert_that) importFrom(assertthat,is.flag) importFrom(dplyr,anti_join) importFrom(dplyr,arrange) importFrom(dplyr,auto_copy) importFrom(dplyr,collapse) importFrom(dplyr,collect) importFrom(dplyr,compute) importFrom(dplyr,copy_to) importFrom(dplyr,count) importFrom(dplyr,db_analyze) importFrom(dplyr,db_create_index) importFrom(dplyr,db_desc) importFrom(dplyr,db_explain) importFrom(dplyr,db_query_fields) importFrom(dplyr,db_save_query) importFrom(dplyr,db_write_table) importFrom(dplyr,distinct) importFrom(dplyr,do) importFrom(dplyr,explain) importFrom(dplyr,filter) importFrom(dplyr,full_join) importFrom(dplyr,group_by) importFrom(dplyr,group_size) importFrom(dplyr,group_vars) importFrom(dplyr,groups) importFrom(dplyr,inner_join) importFrom(dplyr,intersect) importFrom(dplyr,left_join) importFrom(dplyr,mutate) importFrom(dplyr,n) importFrom(dplyr,n_groups) importFrom(dplyr,pull) importFrom(dplyr,relocate) importFrom(dplyr,rename) importFrom(dplyr,rename_with) importFrom(dplyr,right_join) importFrom(dplyr,same_src) importFrom(dplyr,select) importFrom(dplyr,semi_join) importFrom(dplyr,setdiff) importFrom(dplyr,show_query) importFrom(dplyr,slice) importFrom(dplyr,slice_head) importFrom(dplyr,slice_max) importFrom(dplyr,slice_min) importFrom(dplyr,slice_sample) importFrom(dplyr,slice_tail) importFrom(dplyr,sql_join) importFrom(dplyr,sql_select) importFrom(dplyr,sql_semi_join) importFrom(dplyr,sql_set_op) importFrom(dplyr,sql_subquery) importFrom(dplyr,sql_translate_env) importFrom(dplyr,src_tbls) importFrom(dplyr,summarise) importFrom(dplyr,tally) importFrom(dplyr,tbl) importFrom(dplyr,tbl_vars) importFrom(dplyr,transmute) importFrom(dplyr,ungroup) importFrom(dplyr,union) importFrom(dplyr,union_all) importFrom(glue,glue) importFrom(magrittr,"%>%") importFrom(methods,initialize) importFrom(stats,setNames) importFrom(stats,update) importFrom(tibble,as_tibble) importFrom(tibble,tbl_sum) importFrom(tibble,tibble) importFrom(tidyselect,everything) importFrom(utils,head) importFrom(utils,tail) dbplyr/LICENSE0000644000176200001440000000005213734215523012555 0ustar liggesusersYEAR: 2013-2019 COPYRIGHT HOLDER: RStudio dbplyr/README.md0000755000176200001440000000745114032677433013050 0ustar liggesusers # dbplyr [![CRAN status](https://www.r-pkg.org/badges/version/dbplyr)](https://cran.r-project.org/package=dbplyr) [![R build status](https://github.com/tidyverse/dbplyr/workflows/R-CMD-check/badge.svg)](https://github.com/tidyverse/dbplyr/actions) [![Codecov test coverage](https://codecov.io/gh/tidyverse/dbplyr/branch/master/graph/badge.svg)](https://codecov.io/gh/tidyverse/dbplyr?branch=master) ## Overview dbplyr is the database backend for [dplyr](https://dplyr.tidyverse.org). It allows you to use remote database tables as if they are in-memory data frames by automatically converting dplyr code into SQL. To learn more about why you might use dbplyr instead of writing SQL, see `vignette("sql")`. To learn more about the details of the SQL translation, see `vignette("translation-verb")` and `vignette("translation-function")`. ## Installation ``` r # The easiest way to get dbplyr is to install the whole tidyverse: install.packages("tidyverse") # Alternatively, install just dbplyr: install.packages("dbplyr") # Or the the development version from GitHub: # install.packages("devtools") devtools::install_github("tidyverse/dbplyr") ``` ## Usage dbplyr is designed to work with database tables as if they were local data frames. To demonstrate this I’ll first create an in-memory SQLite database and copy over a dataset: ``` r library(dplyr, warn.conflicts = FALSE) con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") copy_to(con, mtcars) ``` Note that you don’t actually need to load dbplyr with `library(dbplyr)`; dplyr automatically loads it for you when it sees you working with a database. Database connections are coordinated by the DBI package. Learn more at Now you can retrieve a table using `tbl()` (see `?tbl_dbi` for more details). Printing it just retrieves the first few rows: ``` r mtcars2 <- tbl(con, "mtcars") mtcars2 #> # Source: table [?? x 11] #> # Database: sqlite 3.34.1 [:memory:] #> mpg cyl disp hp drat wt qsec vs am gear carb #> #> 1 21 6 160 110 3.9 2.62 16.5 0 1 4 4 #> 2 21 6 160 110 3.9 2.88 17.0 0 1 4 4 #> 3 22.8 4 108 93 3.85 2.32 18.6 1 1 4 1 #> 4 21.4 6 258 110 3.08 3.22 19.4 1 0 3 1 #> 5 18.7 8 360 175 3.15 3.44 17.0 0 0 3 2 #> 6 18.1 6 225 105 2.76 3.46 20.2 1 0 3 1 #> 7 14.3 8 360 245 3.21 3.57 15.8 0 0 3 4 #> 8 24.4 4 147. 62 3.69 3.19 20 1 0 4 2 #> 9 22.8 4 141. 95 3.92 3.15 22.9 1 0 4 2 #> 10 19.2 6 168. 123 3.92 3.44 18.3 1 0 4 4 #> # … with more rows ``` All dplyr calls are evaluated lazily, generating SQL that is only sent to the database when you request the data. ``` r # lazily generates query summary <- mtcars2 %>% group_by(cyl) %>% summarise(mpg = mean(mpg, na.rm = TRUE)) %>% arrange(desc(mpg)) # see query summary %>% show_query() #> #> SELECT `cyl`, AVG(`mpg`) AS `mpg` #> FROM `mtcars` #> GROUP BY `cyl` #> ORDER BY `mpg` DESC # execute query and retrieve results summary %>% collect() #> # A tibble: 3 x 2 #> cyl mpg #> #> 1 4 26.7 #> 2 6 19.7 #> 3 8 15.1 ``` ## Code of Conduct Please note that the dbplyr project is released with a [Contributor Code of Conduct](https://dbplyr.tidyverse.org/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. dbplyr/man/0000755000176200001440000000000014031447261012323 5ustar liggesusersdbplyr/man/do.tbl_sql.Rd0000644000176200001440000000150013575012774014660 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-do.R \name{do.tbl_sql} \alias{do.tbl_sql} \title{Perform arbitrary computation on remote backend} \usage{ \method{do}{tbl_sql}(.data, ..., .chunk_size = 10000L) } \arguments{ \item{.data}{a tbl} \item{...}{Expressions to apply to each group. If named, results will be stored in a new column. If unnamed, should return a data frame. You can use \code{.} to refer to the current group. You can not mix named and unnamed arguments.} \item{.chunk_size}{The size of each chunk to pull into R. If this number is too big, the process will be slow because R has to allocate and free a lot of memory. If it's too small, it will be slow, because of the overhead of talking to the database.} } \description{ Perform arbitrary computation on remote backend } dbplyr/man/sql_expr.Rd0000644000176200001440000000255113734215523014455 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/sql-expr.R \name{sql_expr} \alias{sql_expr} \alias{sql_call2} \title{Generate SQL from R expressions} \usage{ sql_expr(x, con = sql_current_con()) sql_call2(.fn, ..., con = sql_current_con()) } \arguments{ \item{x}{A quasiquoted expression} \item{con}{Connection to use for escaping. Will be set automatically when called from a function translation.} \item{.fn}{Function name (as string, call, or symbol)} \item{...}{Arguments to function} } \description{ Low-level building block for generating SQL from R expressions. Strings are escaped; names become bare SQL identifiers. User infix functions have \verb{\%} stripped. } \details{ Using \code{sql_expr()} in package will require use of \code{\link[=globalVariables]{globalVariables()}} to avoid \verb{R CMD check} NOTES. This is a small amount of additional pain, which I think is worthwhile because it leads to more readable translation code. } \examples{ con <- simulate_dbi() # not necessary when writing translations sql_expr(f(x + 1), con = con) sql_expr(f("x", "y"), con = con) sql_expr(f(x, y), con = con) x <- ident("x") sql_expr(f(!!x, y), con = con) sql_expr(cast("x" \%as\% DECIMAL), con = con) sql_expr(round(x) \%::\% numeric, con = con) sql_call2("+", quote(x), 1, con = con) sql_call2("+", "x", 1, con = con) } \keyword{internal} dbplyr/man/testing.Rd0000644000176200001440000000156614002647450014277 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/test-frame.R \name{testing} \alias{testing} \alias{test_register_src} \alias{test_register_con} \alias{src_test} \alias{test_load} \alias{test_frame} \title{Infrastructure for testing dplyr} \usage{ test_register_src(name, src) test_register_con(name, ...) src_test(name) test_load( df, name = unique_table_name(), srcs = test_srcs$get(), ignore = character() ) test_frame(..., srcs = test_srcs$get(), ignore = character()) } \description{ Register testing sources, then use \code{test_load()} to load an existing data frame into each source. To create a new table in each source, use \code{test_frame()}. } \examples{ \dontrun{ test_register_src("sqlite", { DBI::dbConnect(RSQLite::SQLite(), ":memory:", create = TRUE) }) test_frame(x = 1:3, y = 3:1) test_load(mtcars) } } \keyword{internal} dbplyr/man/pivot_longer.tbl_lazy.Rd0000644000176200001440000000601114004012136017123 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-pivot-longer.R \name{pivot_longer.tbl_lazy} \alias{pivot_longer.tbl_lazy} \title{Pivot data from wide to long} \usage{ pivot_longer.tbl_lazy( data, cols, names_to = "name", names_prefix = NULL, names_sep = NULL, names_pattern = NULL, names_ptypes = list(), names_transform = list(), names_repair = "check_unique", values_to = "value", values_drop_na = FALSE, values_ptypes, values_transform = list(), ... ) } \arguments{ \item{data}{A data frame to pivot.} \item{cols}{Columns to pivot into longer format.} \item{names_to}{A string specifying the name of the column to create from the data stored in the column names of \code{data}.} \item{names_prefix}{A regular expression used to remove matching text from the start of each variable name.} \item{names_sep, names_pattern}{If \code{names_to} contains multiple values, these arguments control how the column name is broken up.} \item{names_ptypes}{A list of column name-prototype pairs.} \item{names_transform, values_transform}{A list of column name-function pairs.} \item{names_repair}{What happens if the output has invalid column names?} \item{values_to}{A string specifying the name of the column to create from the data stored in cell values. If \code{names_to} is a character containing the special \code{.value} sentinel, this value will be ignored, and the name of the value column will be derived from part of the existing column names.} \item{values_drop_na}{If \code{TRUE}, will drop rows that contain only \code{NA}s in the \code{value_to} column.} \item{values_ptypes}{Not supported.} \item{...}{Additional arguments passed on to methods.} } \description{ \code{pivot_longer()} "lengthens" data, increasing the number of rows and decreasing the number of columns. The inverse transformation is `tidyr::pivot_wider()] Learn more in \code{vignette("pivot", "tidyr")}. While most functionality is identical there are some differences to \code{pivot_longer()} on local data frames: \itemize{ \item the output is sorted differently/not explicitly, \item the coercion of mixed column types is left to the database, \item \code{values_ptypes} NOT supported. } Note that \code{build_longer_spec()} and \code{pivot_longer_spec()} do not work with remote tables. } \details{ The SQL translation basically works as follows: \enumerate{ \item split the specification by its key columns i.e. by variables crammed into the column names. \item for each part in the splitted specification \code{transmute()} \code{data} into the following columns } \itemize{ \item id columns i.e. columns that are not pivotted \item key columns \item value columns i.e. columns that are pivotted } \enumerate{ \item combine all the parts with \code{union_all()} } } \examples{ # See vignette("pivot") for examples and explanation # Simplest case where column names are character data if (require("tidyr", quietly = TRUE)) { memdb_frame( id = c("a", "b"), x = 1:2, y = 3:4 ) \%>\% pivot_longer(-id) } } dbplyr/man/filter.tbl_lazy.Rd0000644000176200001440000000207714015732330015720 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-filter.R \name{filter.tbl_lazy} \alias{filter.tbl_lazy} \title{Subset rows using column values} \usage{ \method{filter}{tbl_lazy}(.data, ..., .preserve = FALSE) } \arguments{ \item{.data}{A lazy data frame backed by a database query.} \item{...}{<\code{\link[dplyr:dplyr_data_masking]{data-masking}}> Variables, or functions of variables. Use \code{\link[dplyr:desc]{desc()}} to sort a variable in descending order.} \item{.preserve}{Not supported by this method.} } \value{ Another \code{tbl_lazy}. Use \code{\link[=show_query]{show_query()}} to see the generated query, and use \code{\link[=collect.tbl_sql]{collect()}} to execute the query and return data to R. } \description{ This is a method for the dplyr \code{\link[=filter]{filter()}} generic. It generates the \code{WHERE} clause of the SQL query. } \examples{ library(dplyr, warn.conflicts = FALSE) db <- memdb_frame(x = c(2, NA, 5, NA, 10), y = 1:5) db \%>\% filter(x < 5) \%>\% show_query() db \%>\% filter(is.na(x)) \%>\% show_query() } dbplyr/man/backend-odbc.Rd0000644000176200001440000000137514002647450015114 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-odbc.R \name{backend-odbc} \alias{simulate_odbc} \title{Backend: ODBC} \usage{ simulate_odbc() } \description{ See \code{vignette("translate-function")} and \code{vignette("translate-verb")} for details of overall translation technology. Key differences for this backend are minor translations for common data types. Use \code{simulate_odbc()} with \code{lazy_frame()} to see simulated SQL without converting to live access database. } \examples{ library(dplyr, warn.conflicts = FALSE) lf <- lazy_frame(a = TRUE, b = 1, d = 2, c = "z", con = simulate_odbc()) lf \%>\% transmute(x = as.numeric(b)) lf \%>\% transmute(x = as.integer(b)) lf \%>\% transmute(x = as.character(b)) } dbplyr/man/collapse.tbl_sql.Rd0000644000176200001440000000343514002647450016060 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-compute.R \name{collapse.tbl_sql} \alias{collapse.tbl_sql} \alias{compute.tbl_sql} \alias{collect.tbl_sql} \title{Compute results of a query} \usage{ \method{collapse}{tbl_sql}(x, ...) \method{compute}{tbl_sql}( x, name = unique_table_name(), temporary = TRUE, unique_indexes = list(), indexes = list(), analyze = TRUE, ... ) \method{collect}{tbl_sql}(x, ..., n = Inf, warn_incomplete = TRUE) } \arguments{ \item{x}{A lazy data frame backed by a database query.} \item{...}{other parameters passed to methods.} \item{name}{Table name in remote database.} \item{temporary}{Should the table be temporary (\code{TRUE}, the default\verb{) or persistent (}FALSE`)?} \item{unique_indexes}{a list of character vectors. Each element of the list will create a new unique index over the specified column(s). Duplicate rows will result in failure.} \item{indexes}{a list of character vectors. Each element of the list will create a new index.} \item{analyze}{if \code{TRUE} (the default), will automatically ANALYZE the new table so that the query optimiser has useful information.} \item{n}{Number of rows to fetch. Defaults to \code{Inf}, meaning all rows.} \item{warn_incomplete}{Warn if \code{n} is less than the number of result rows?} } \description{ These are methods for the dplyr generics \code{\link[=collapse]{collapse()}}, \code{\link[=compute]{compute()}}, and \code{\link[=collect]{collect()}}. \code{collapse()} creates a subquery, \code{compute()} stores the results in a remote table, and \code{collect()} executes the query and downloads the data into R. } \examples{ library(dplyr, warn.conflicts = FALSE) db <- memdb_frame(a = c(3, 4, 1, 2), b = c(5, 1, 2, NA)) db \%>\% filter(a <= 2) \%>\% collect() } dbplyr/man/select.tbl_lazy.Rd0000644000176200001440000000372014015732330015706 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-select.R \name{select.tbl_lazy} \alias{select.tbl_lazy} \alias{rename.tbl_lazy} \alias{rename_with.tbl_lazy} \alias{relocate.tbl_lazy} \title{Subset, rename, and reorder columns using their names} \usage{ \method{select}{tbl_lazy}(.data, ...) \method{rename}{tbl_lazy}(.data, ...) \method{rename_with}{tbl_lazy}(.data, .fn, .cols = everything(), ...) \method{relocate}{tbl_lazy}(.data, ..., .before = NULL, .after = NULL) } \arguments{ \item{.data}{A lazy data frame backed by a database query.} \item{...}{<\code{\link[dplyr:dplyr_data_masking]{data-masking}}> Variables, or functions of variables. Use \code{\link[dplyr:desc]{desc()}} to sort a variable in descending order.} \item{.fn}{A function used to transform the selected \code{.cols}. Should return a character vector the same length as the input.} \item{.cols}{<\code{\link[dplyr:dplyr_tidy_select]{tidy-select}}> Columns to rename; defaults to all columns.} \item{.before}{<\code{\link[dplyr:dplyr_tidy_select]{tidy-select}}> Destination of columns selected by \code{...}. Supplying neither will move columns to the left-hand side; specifying both is an error.} \item{.after}{<\code{\link[dplyr:dplyr_tidy_select]{tidy-select}}> Destination of columns selected by \code{...}. Supplying neither will move columns to the left-hand side; specifying both is an error.} } \description{ These are methods for the dplyr \code{\link[=select]{select()}}, \code{\link[=rename]{rename()}}, and \code{\link[=relocate]{relocate()}} generics. They generate the \code{SELECT} clause of the SQL query. These functions do not support predicate functions, i.e. you can not use \code{where(is.numeric)} to select all numeric variables. } \examples{ library(dplyr, warn.conflicts = FALSE) db <- memdb_frame(x = 1, y = 2, z = 3) db \%>\% select(-y) \%>\% show_query() db \%>\% relocate(z) \%>\% show_query() db \%>\% rename(first = x, last = z) \%>\% show_query() } dbplyr/man/backend-impala.Rd0000644000176200001440000000130514002647450015441 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-impala.R \name{backend-impala} \title{Backend: Impala} \description{ See \code{vignette("translate-function")} and \code{vignette("translate-verb")} for details of overall translation technology. Key differences for this backend are a scattering of custom translations provided by users, mostly focussed on bitwise operations. Use \code{simulate_impala()} with \code{lazy_frame()} to see simulated SQL without converting to live access database. } \examples{ library(dplyr, warn.conflicts = FALSE) lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_impala()) lf \%>\% transmute(X = bitwNot(bitwOr(b, c))) } dbplyr/man/replace_na.tbl_lazy.Rd0000644000176200001440000000170314004012136016510 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-expand.R \name{replace_na.tbl_lazy} \alias{replace_na.tbl_lazy} \title{Replace NAs with specified values} \usage{ replace_na.tbl_lazy(data, replace = list(), ...) } \arguments{ \item{data}{A pair of lazy data frame backed by database queries.} \item{replace}{A named list of values, with one value for each column that has NA values to be replaced.} \item{...}{Unused; included for compatibility with generic.} } \value{ Another \code{tbl_lazy}. Use \code{\link[=show_query]{show_query()}} to see the generated query, and use \code{\link[=collect.tbl_sql]{collect()}} to execute the query and return data to R. } \description{ This is a method for the \code{\link[tidyr:replace_na]{tidyr::replace_na()}} generic. } \examples{ if (require("tidyr", quietly = TRUE)) { df <- memdb_frame(x = c(1, 2, NA), y = c("a", NA, "b")) df \%>\% replace_na(list(x = 0, y = "unknown")) } } dbplyr/man/backend-hana.Rd0000644000176200001440000000177214002647450015115 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-hana.R \name{backend-hana} \alias{simulate_hana} \title{Backend: SAP HANA} \usage{ simulate_hana() } \description{ See \code{vignette("translate-function")} and \code{vignette("translate-verb")} for details of overall translation technology. Key differences for this backend are: \itemize{ \item Temporary tables get \verb{#} prefix and use \verb{LOCAL TEMPORARY COLUMN}. \item No table analysis performed in \code{\link[=copy_to]{copy_to()}}. \item \code{paste()} uses \code{||} \item Note that you can't create new boolean columns from logical expressions; you need to wrap with explicit \code{ifelse}: \code{ifelse(x > y, TRUE, FALSE)}. } Use \code{simulate_hana()} with \code{lazy_frame()} to see simulated SQL without converting to live access database. } \examples{ library(dplyr, warn.conflicts = FALSE) lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_hana()) lf \%>\% transmute(x = paste0(z, " times")) } dbplyr/man/head.tbl_lazy.Rd0000644000176200001440000000255714002647450015343 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-head.R \name{head.tbl_lazy} \alias{head.tbl_lazy} \title{Subset the first rows} \usage{ \method{head}{tbl_lazy}(x, n = 6L, ...) } \arguments{ \item{x}{A lazy data frame backed by a database query.} \item{n}{Number of rows to return} \item{...}{Not used.} } \value{ Another \code{tbl_lazy}. Use \code{\link[=show_query]{show_query()}} to see the generated query, and use \code{\link[=collect.tbl_sql]{collect()}} to execute the query and return data to R. } \description{ This is a method for the \code{\link[=head]{head()}} generic. It is usually translated to the \code{LIMIT} clause of the SQL query. Because \code{LIMIT} is not an official part of the SQL specification, some database use other clauses like \code{TOP} or \verb{FETCH ROWS}. Note that databases don't really have a sense of row order, so what "first" means is subject to interpretation. Most databases will respect ordering performed with \code{arrange()}, but it's not guaranteed. \code{tail()} is not supported at all because the situation is even murkier for the "last" rows. } \examples{ library(dplyr, warn.conflicts = FALSE) db <- memdb_frame(x = 1:100) db \%>\% head() \%>\% show_query() # Pretend we have data in a SQL server database db2 <- lazy_frame(x = 1:100, con = simulate_mssql()) db2 \%>\% head() \%>\% show_query() } dbplyr/man/db-misc.Rd0000644000176200001440000000234114002647450014130 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/db.R \name{db-misc} \alias{db_connection_describe} \alias{sql_join_suffix} \alias{db_sql_render} \alias{dbplyr_edition} \title{Miscellaneous database generics} \usage{ db_connection_describe(con) sql_join_suffix(con, ...) db_sql_render(con, sql, ...) dbplyr_edition(con) } \description{ \itemize{ \item \code{db_connection_describe()} provides a short string describing the database connection, helping users tell which database a table comes from. It should be a single line, and ideally less than 60 characters wide. } } \details{ \itemize{ \item \code{dbplyr_edition()} declares which version of the dbplyr API you want. See below for more details. } } \section{dplyr 2.0.0}{ dplyr 2.0.0 renamed a number of generics so that they could be cleanly moved from dplyr to dbplyr. If you have an existing backend, you'll need to rename the following methods. \itemize{ \item \code{dplyr::db_desc()} -> \code{dbplyr::db_connection_describe()} (also note that the argument named changed from \code{x} to \code{con}). } } \seealso{ Other generic: \code{\link{db-sql}}, \code{\link{db_copy_to}()}, \code{\link{sql_escape_logical}()} } \concept{generic} \keyword{internal} dbplyr/man/sql_build.Rd0000644000176200001440000000450314002647450014572 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/query-join.R, R/query-select.R, % R/query-semi-join.R, R/query-set-op.R, R/sql-build.R \name{join_query} \alias{join_query} \alias{select_query} \alias{semi_join_query} \alias{set_op_query} \alias{sql_build} \alias{sql_render} \alias{sql_optimise} \title{Build and render SQL from a sequence of lazy operations} \usage{ join_query( x, y, vars, type = "inner", by = NULL, suffix = c(".x", ".y"), na_matches = FALSE ) select_query( from, select = sql("*"), where = character(), group_by = character(), having = character(), order_by = character(), limit = NULL, distinct = FALSE ) semi_join_query(x, y, anti = FALSE, by = NULL, na_matches = FALSE) set_op_query(x, y, type = type, all = FALSE) sql_build(op, con = NULL, ...) sql_render(query, con = NULL, ..., subquery = FALSE) sql_optimise(x, con = NULL, ..., subquery = FALSE) } \arguments{ \item{op}{A sequence of lazy operations} \item{con}{A database connection. The default \code{NULL} uses a set of rules that should be very similar to ANSI 92, and allows for testing without an active database connection.} \item{...}{Other arguments passed on to the methods. Not currently used.} \item{subquery}{Is this SQL going to be used in a subquery? This is important because you can place a bare table name in a subquery and ORDER BY does not work in subqueries.} } \description{ \code{sql_build()} creates a \code{select_query} S3 object, that is rendered to a SQL string by \code{sql_render()}. The output from \code{sql_build()} is designed to be easy to test, as it's database agnostic, and has a hierarchical structure. Outside of testing, however, you should always call \code{sql_render()}. } \details{ \code{sql_build()} is generic over the lazy operations, \link{lazy_ops}, and generates an S3 object that represents the query. \code{sql_render()} takes a query object and then calls a function that is generic over the database. For example, \code{sql_build.op_mutate()} generates a \code{select_query}, and \code{sql_render.select_query()} calls \code{sql_select()}, which has different methods for different databases. The default methods should generate ANSI 92 SQL where possible, so you backends only need to override the methods if the backend is not ANSI compliant. } \keyword{internal} dbplyr/man/dbplyr-package.Rd0000644000176200001440000000202214004012136015460 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/dbplyr.R \docType{package} \name{dbplyr-package} \alias{dbplyr} \alias{dbplyr-package} \title{dbplyr: A 'dplyr' Back End for Databases} \description{ \if{html}{\figure{logo.png}{options: align='right' alt='logo' width='120'}} A 'dplyr' back end for databases that allows you to work with remote database tables as if they are in-memory data frames. Basic features works with any database that has a 'DBI' back end; more advanced features require 'SQL' translation to be provided by the package author. } \seealso{ Useful links: \itemize{ \item \url{https://dbplyr.tidyverse.org/} \item \url{https://github.com/tidyverse/dbplyr} \item Report bugs at \url{https://github.com/tidyverse/dbplyr/issues} } } \author{ \strong{Maintainer}: Hadley Wickham \email{hadley@rstudio.com} Authors: \itemize{ \item Maximilian Girlich \item Edgar Ruiz } Other contributors: \itemize{ \item RStudio [copyright holder, funder] } } \keyword{internal} dbplyr/man/db-sql.Rd0000644000176200001440000001046014002647450013775 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/db-sql.R \name{db-sql} \alias{db-sql} \alias{sql_expr_matches} \alias{sql_translation} \alias{sql_table_analyze} \alias{sql_table_index} \alias{sql_query_explain} \alias{sql_query_fields} \alias{sql_query_save} \alias{sql_query_wrap} \alias{sql_query_rows} \alias{sql_query_select} \alias{sql_query_join} \alias{sql_query_semi_join} \alias{sql_query_set_op} \title{SQL generation generics} \usage{ sql_expr_matches(con, x, y) sql_translation(con) sql_table_analyze(con, table, ...) sql_table_index(con, table, columns, name = NULL, unique = FALSE, ...) sql_query_explain(con, sql, ...) sql_query_fields(con, sql, ...) sql_query_save(con, sql, name, temporary = TRUE, ...) sql_query_wrap(con, from, name = unique_subquery_name(), ...) sql_query_rows(con, sql, ...) sql_query_select( con, select, from, where = NULL, group_by = NULL, having = NULL, order_by = NULL, limit = NULL, distinct = FALSE, ..., subquery = FALSE ) sql_query_join( con, x, y, vars, type = "inner", by = NULL, na_matches = FALSE, ... ) sql_query_semi_join(con, x, y, anti = FALSE, by = NULL, ...) sql_query_set_op(con, x, y, method, ..., all = FALSE) } \description{ SQL translation: \itemize{ \item \code{sql_expr_matches(con, x, y)} generates an alternative to \code{x = y} when a pair of \code{NULL}s should match. The default translation uses a \verb{CASE WHEN} as described in \url{https://modern-sql.com/feature/is-distinct-from}. \item \code{sql_translation(con)} generates a SQL translation environment. } Tables: \itemize{ \item \code{sql_table_analyze(con, table)} generates SQL that "analyzes" the table, ensuring that the database has up-to-date statistics for use in the query planner. It called from \code{\link[=copy_to]{copy_to()}} when \code{analyze = TRUE}. \item \code{sql_table_index()} generates SQL for adding an index to table. The } Query manipulation: \itemize{ \item \code{sql_query_explain(con, sql)} generates SQL that "explains" a query, i.e. generates a query plan describing what indexes etc that the database will use. \item \code{sql_query_fields()} generates SQL for a 0-row result that is used to capture field names in \code{\link[=tbl_sql]{tbl_sql()}} \item \code{sql_query_save(con, sql)} generates SQL for saving a query into a (temporary) table. \item \code{sql_query_wrap(con, from)} generates SQL for wrapping a query into a subquery. } Query generation: \itemize{ \item \code{sql_query_select()} generate SQL for a \code{SELECT} query \item \code{sql_query_join()} generate SQL for joins \item \code{sql_query_semi_join()} generate SQL for semi- and anti-joins \item \code{sql_query_set_op()} generate SQL for \code{UNION}, \code{INTERSECT}, and \code{EXCEPT} queries. } } \section{dbplyr 2.0.0}{ Many \verb{dplyr::db_*} generics have been replaced by \verb{dbplyr::sql_*} generics. To update your backend, you'll need to extract the SQL generation out of your existing code, and place it in a new method for a dbplyr \code{sql_} generic. \itemize{ \item \code{dplyr::db_analyze()} is replaced by \code{dbplyr::sql_table_analyze()} \item \code{dplyr::db_explain()} is replaced by \code{dbplyr::sql_query_explain()} \item \code{dplyr::db_create_index()} is replaced by \code{dbplyr::sql_table_index()} \item \code{dplyr::db_query_fields()} is replaced by \code{dbplyr::sql_query_fields()} \item \code{dplyr::db_query_rows()} is no longer used; you can delete it \item \code{dplyr::db_save_query()} is replaced by \code{dbplyr::sql_query_save()} } The query generating functions have also changed names. Their behaviour is unchanged, so you just need to rename the generic and import from dbplyr instead of dplyr. \itemize{ \item \code{dplyr::sql_select()} is replaced by \code{dbplyr::sql_query_select()} \item \code{dplyr::sql_join()} is replaced by \code{dbplyr::sql_query_join()} \item \code{dplyr::sql_semi_join()} is replaced by \code{dbplyr::sql_query_semi_join()} \item \code{dplyr::sql_set_op()} is replaced by \code{dbplyr::sql_query_set_op()} \item \code{dplyr::sql_subquery()} is replaced by \code{dbplyr::sql_query_wrap()} } Learn more in \code{vignette("backend-2.0")} } \seealso{ Other generic: \code{\link{db_connection_describe}()}, \code{\link{db_copy_to}()}, \code{\link{sql_escape_logical}()} } \concept{generic} \keyword{internal} dbplyr/man/ident_q.Rd0000644000176200001440000000044114002647450014234 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/schema.R \name{ident_q} \alias{ident_q} \title{Declare a identifer as being pre-quoted.} \usage{ ident_q(...) } \description{ No longer needed; please use \code{\link[=sql]{sql()}} instead. } \keyword{internal} dbplyr/man/mutate.tbl_lazy.Rd0000644000176200001440000000225214015732330015725 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-mutate.R \name{mutate.tbl_lazy} \alias{mutate.tbl_lazy} \title{Create, modify, and delete columns} \usage{ \method{mutate}{tbl_lazy}(.data, ...) } \arguments{ \item{.data}{A lazy data frame backed by a database query.} \item{...}{<\code{\link[dplyr:dplyr_data_masking]{data-masking}}> Variables, or functions of variables. Use \code{\link[dplyr:desc]{desc()}} to sort a variable in descending order.} } \value{ Another \code{tbl_lazy}. Use \code{\link[=show_query]{show_query()}} to see the generated query, and use \code{\link[=collect.tbl_sql]{collect()}} to execute the query and return data to R. } \description{ These are methods for the dplyr \code{\link[=mutate]{mutate()}} and \code{\link[=transmute]{transmute()}} generics. They are translated to computed expressions in the \code{SELECT} clause of the SQL query. } \examples{ library(dplyr, warn.conflicts = FALSE) db <- memdb_frame(x = 1:5, y = 5:1) db \%>\% mutate(a = (x + y) / 2, b = sqrt(x^2L + y^2L)) \%>\% show_query() # dbplyr automatically creates subqueries as needed db \%>\% mutate(x1 = x + 1, x2 = x1 * 2) \%>\% show_query() } dbplyr/man/nycflights13.Rd0000644000176200001440000000153514002647450015134 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/data-nycflights13.R \name{nycflights13} \alias{nycflights13} \alias{nycflights13_sqlite} \alias{nycflights13_postgres} \alias{has_nycflights13} \alias{copy_nycflights13} \title{Database versions of the nycflights13 data} \usage{ nycflights13_sqlite(path = NULL) nycflights13_postgres(dbname = "nycflights13", ...) has_nycflights13(type = c("sqlite", "postgresql"), ...) copy_nycflights13(con, ...) } \arguments{ \item{path}{location of SQLite database file} \item{dbname, ...}{Arguments passed on to \code{\link[=src_postgres]{src_postgres()}}} } \description{ These functions cache the data from the \code{nycflights13} database in a local database, for use in examples and vignettes. Indexes are created to making joining tables on natural keys efficient. } \keyword{internal} dbplyr/man/pivot_wider.tbl_lazy.Rd0000644000176200001440000000630714004012136016757 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-pivot-wider.R \name{pivot_wider.tbl_lazy} \alias{pivot_wider.tbl_lazy} \title{Pivot data from long to wide} \usage{ pivot_wider.tbl_lazy( data, id_cols = NULL, names_from = name, names_prefix = "", names_sep = "_", names_glue = NULL, names_sort = FALSE, names_repair = "check_unique", values_from = value, values_fill = NULL, values_fn = max, ... ) } \arguments{ \item{data}{A lazy data frame backed by a database query.} \item{id_cols}{A set of columns that uniquely identifies each observation.} \item{names_from, values_from}{A pair of arguments describing which column (or columns) to get the name of the output column (\code{names_from}), and which column (or columns) to get the cell values from (\code{values_from}). If \code{values_from} contains multiple values, the value will be added to the front of the output column.} \item{names_prefix}{String added to the start of every variable name.} \item{names_sep}{If \code{names_from} or \code{values_from} contains multiple variables, this will be used to join their values together into a single string to use as a column name.} \item{names_glue}{Instead of \code{names_sep} and \code{names_prefix}, you can supply a glue specification that uses the \code{names_from} columns (and special \code{.value}) to create custom column names.} \item{names_sort}{Should the column names be sorted? If \code{FALSE}, the default, column names are ordered by first appearance.} \item{names_repair}{What happens if the output has invalid column names?} \item{values_fill}{Optionally, a (scalar) value that specifies what each \code{value} should be filled in with when missing.} \item{values_fn}{A function, the default is \code{max()}, applied to the \code{value} in each cell in the output. In contrast to local data frames it must not be \code{NULL}.} \item{...}{Unused; included for compatibility with generic.} } \description{ \code{pivot_wider()} "widens" data, increasing the number of columns and decreasing the number of rows. The inverse transformation is \code{pivot_longer()}. Learn more in \code{vignette("pivot", "tidyr")}. } \details{ The big difference to \code{pivot_wider()} for local data frames is that \code{values_fn} must not be \code{NULL}. By default it is \code{max()} which yields the same results as for local data frames if the combination of \code{id_cols} and \code{value} column uniquely identify an observation. Mind that you also do not get a warning if an observation is not uniquely identified. The translation to SQL code basically works as follows: \enumerate{ \item Get unique keys in \code{names_from} column. \item For each key value generate an expression of the form:\if{html}{\out{
}}\preformatted{value_fn( CASE WHEN (`names from column` == `key value`) THEN (`value column`) END ) AS `output column` }\if{html}{\out{
}} \item Group data by id columns. \item Summarise the grouped data with the expressions from step 2. } } \examples{ if (require("tidyr", quietly = TRUE)) { memdb_frame( id = 1, key = c("x", "y"), value = 1:2 ) \%>\% tidyr::pivot_wider( id_cols = id, names_from = key, values_from = value ) } } dbplyr/man/backend-hive.Rd0000644000176200001440000000150214002647450015130 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-hive.R \name{backend-hive} \title{Backend: Hive} \description{ See \code{vignette("translate-function")} and \code{vignette("translate-verb")} for details of overall translation technology. Key differences for this backend are a scattering of custom translations provided by users. Use \code{simulate_hive()} with \code{lazy_frame()} to see simulated SQL without converting to live access database. } \examples{ library(dplyr, warn.conflicts = FALSE) lf <- lazy_frame(a = TRUE, b = 1, d = 2, c = "z", con = simulate_hive()) lf \%>\% transmute(x = cot(b)) lf \%>\% transmute(x = bitwShiftL(c, 1L)) lf \%>\% transmute(x = str_replace_all(z, "a", "b")) lf \%>\% summarise(x = median(d, na.rm = TRUE)) lf \%>\% summarise(x = var(c, na.rm = TRUE)) } dbplyr/man/sql.Rd0000644000176200001440000000124014002647450013406 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/sql.R \name{sql} \alias{sql} \alias{is.sql} \alias{as.sql} \title{SQL escaping.} \usage{ sql(...) is.sql(x) as.sql(x, con) } \arguments{ \item{...}{Character vectors that will be combined into a single SQL expression.} \item{x}{Object to coerce} \item{con}{Needed when \code{x} is directly suppled from the user so that schema specifications can be quoted using the correct identifiers.} } \description{ These functions are critical when writing functions that translate R functions to sql functions. Typically a conversion function should escape all its inputs and return an sql object. } dbplyr/man/tbl_sql.Rd0000644000176200001440000000126314002647450014254 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tbl-sql.R \name{tbl_sql} \alias{tbl_sql} \title{Create an SQL tbl (abstract)} \usage{ tbl_sql(subclass, src, from, ..., vars = NULL) } \arguments{ \item{subclass}{name of subclass} \item{...}{needed for agreement with generic. Not otherwise used.} \item{vars}{Provide column names as a character vector to avoid retrieving them from the database. Mainly useful for better performance when creating multiple \code{tbl} objects.} } \description{ Generally, you should no longer need to provide a custom \code{tbl()} method. The default \code{tbl.DBIConnect} method should work in most cases. } \keyword{internal} dbplyr/man/count.tbl_lazy.Rd0000644000176200001440000000334014015732330015555 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-count.R \name{count.tbl_lazy} \alias{count.tbl_lazy} \alias{tally.tbl_lazy} \title{Count observations by group} \usage{ \method{count}{tbl_lazy}(x, ..., wt = NULL, sort = FALSE, name = NULL) \method{tally}{tbl_lazy}(x, wt = NULL, sort = FALSE, name = NULL) } \arguments{ \item{x}{A data frame, data frame extension (e.g. a tibble), or a lazy data frame (e.g. from dbplyr or dtplyr).} \item{...}{<\code{\link[dplyr:dplyr_data_masking]{data-masking}}> Variables, or functions of variables. Use \code{\link[dplyr:desc]{desc()}} to sort a variable in descending order.} \item{wt}{<\code{\link[dplyr:dplyr_data_masking]{data-masking}}> Frequency weights. Can be \code{NULL} or a variable: \itemize{ \item If \code{NULL} (the default), counts the number of rows in each group. \item If a variable, computes \code{sum(wt)} for each group. }} \item{sort}{If \code{TRUE}, will show the largest groups at the top.} \item{name}{The name of the new column in the output. If omitted, it will default to \code{n}. If there's already a column called \code{n}, it will error, and require you to specify the name.} } \description{ These are methods for the dplyr \code{\link[=count]{count()}} and \code{\link[=tally]{tally()}} generics. They wrap up \code{\link[=group_by.tbl_lazy]{group_by.tbl_lazy()}}, \code{\link[=summarise.tbl_lazy]{summarise.tbl_lazy()}} and, optionally, \code{\link[=arrange.tbl_lazy]{arrange.tbl_lazy()}}. } \examples{ library(dplyr, warn.conflicts = FALSE) db <- memdb_frame(g = c(1, 1, 1, 2, 2), x = c(4, 3, 6, 9, 2)) db \%>\% count(g) \%>\% show_query() db \%>\% count(g, wt = x) \%>\% show_query() db \%>\% count(g, wt = x, sort = TRUE) \%>\% show_query() } dbplyr/man/backend-mssql.Rd0000644000176200001440000000461714002647450015346 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-mssql.R \name{backend-mssql} \title{Backend: SQL server} \arguments{ \item{version}{Version of MS SQL to simulate. Currently only, difference is that 15.0 and above will use \code{TRY_CAST()} instead of \code{CAST()}.} } \description{ See \code{vignette("translate-function")} and \code{vignette("translate-verb")} for details of overall translation technology. Key differences for this backend are: \itemize{ \item \code{SELECT} uses \code{TOP} not \code{LIMIT} \item Automatically prefixes \verb{#} to create temporary tables. Add the prefix yourself to avoid the message. \item String basics: \code{paste()}, \code{substr()}, \code{nchar()} \item Custom types for \verb{as.*} functions \item Lubridate extraction functions, \code{year()}, \code{month()}, \code{day()} etc \item Semi-automated bit <-> boolean translation (see below) } Use \code{simulate_mssql()} with \code{lazy_frame()} to see simulated SQL without converting to live access database. } \section{Bit vs boolean}{ SQL server uses two incompatible types to represent \code{TRUE} and \code{FALSE} values: \itemize{ \item The \code{BOOLEAN} type is the result of logical comparisons (e.g. \code{x > y}) and can be used \code{WHERE} but not to create new columns in \code{SELECT}. \url{https://docs.microsoft.com/en-us/sql/t-sql/language-elements/comparison-operators-transact-sql} \item The \code{BIT} type is a special type of numeric column used to store \code{TRUE} and \code{FALSE} values, but can't be used in \code{WHERE} clauses. \url{https://docs.microsoft.com/en-us/sql/t-sql/data-types/bit-transact-sql?view=sql-server-ver15} } dbplyr does its best to automatically create the correct type when needed, but can't do it 100\% correctly because it does not have a full type inference system. This means that you many need to manually do conversions from time to time. \itemize{ \item To convert from bit to boolean use \code{x == 1} \item To convert from boolean to bit use \verb{as.logical(if(x, 0, 1))} } } \examples{ library(dplyr, warn.conflicts = FALSE) lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_mssql()) lf \%>\% head() lf \%>\% transmute(x = paste(b, c, d)) # Can use boolean as is: lf \%>\% filter(c > d) # Need to convert from boolean to bit: lf \%>\% transmute(x = c > d) # Can use boolean as is: lf \%>\% transmute(x = ifelse(c > d, "c", "d")) } dbplyr/man/arrange.tbl_lazy.Rd0000644000176200001440000000326314015732330016050 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-arrange.R \name{arrange.tbl_lazy} \alias{arrange.tbl_lazy} \title{Arrange rows by column values} \usage{ \method{arrange}{tbl_lazy}(.data, ..., .by_group = FALSE) } \arguments{ \item{.data}{A lazy data frame backed by a database query.} \item{...}{<\code{\link[dplyr:dplyr_data_masking]{data-masking}}> Variables, or functions of variables. Use \code{\link[dplyr:desc]{desc()}} to sort a variable in descending order.} \item{.by_group}{If \code{TRUE}, will sort first by grouping variable. Applies to grouped data frames only.} } \value{ Another \code{tbl_lazy}. Use \code{\link[=show_query]{show_query()}} to see the generated query, and use \code{\link[=collect.tbl_sql]{collect()}} to execute the query and return data to R. } \description{ This is an method for the dplyr \code{\link[=arrange]{arrange()}} generic. It generates the \verb{ORDER BY} clause of the SQL query. It also affects the \code{\link[=window_order]{window_order()}} of windowed expressions in \code{\link[=mutate.tbl_lazy]{mutate.tbl_lazy()}}. Note that \verb{ORDER BY} clauses can not generally appear in subqueries, which means that you should \code{arrange()} as late as possible in your pipelines. } \section{Missing values}{ Unlike R, most databases sorts \code{NA} (\code{NULL}s) at the front. You can can override this behaviour by explicitly sorting on \code{is.na(x)}. } \examples{ library(dplyr, warn.conflicts = FALSE) db <- memdb_frame(a = c(3, 4, 1, 2), b = c(5, 1, 2, NA)) db \%>\% arrange(a) \%>\% show_query() # Note that NAs are sorted first db \%>\% arrange(b) # override by sorting on is.na() first db \%>\% arrange(is.na(b), b) } dbplyr/man/sql_quote.Rd0000644000176200001440000000103413416416356014633 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/escape.R \name{sql_quote} \alias{sql_quote} \title{Helper function for quoting sql elements.} \usage{ sql_quote(x, quote) } \arguments{ \item{x}{Character vector to escape.} \item{quote}{Single quoting character.} } \description{ If the quote character is present in the string, it will be doubled. \code{NA}s will be replaced with NULL. } \examples{ sql_quote("abc", "'") sql_quote("I've had a good day", "'") sql_quote(c("abc", NA), "'") } \keyword{internal} dbplyr/man/db-io.Rd0000644000176200001440000000427314002647450013612 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/db-io.R \name{db-io} \alias{db_copy_to} \alias{db_compute} \alias{db_collect} \alias{db_table_temporary} \title{Database I/O generics} \usage{ db_copy_to( con, table, values, overwrite = FALSE, types = NULL, temporary = TRUE, unique_indexes = NULL, indexes = NULL, analyze = TRUE, ..., in_transaction = TRUE ) db_compute( con, table, sql, temporary = TRUE, unique_indexes = list(), indexes = list(), analyze = TRUE, ... ) db_collect(con, sql, n = -1, warn_incomplete = TRUE, ...) db_table_temporary(con, table, temporary) } \description{ These generics are responsible for getting data into and out of the database. They should be used a last resort - only use them when you can't make a backend work by providing methods for DBI generics, or for dbplyr's SQL generation generics. They tend to be most needed when a backend has special handling of temporary tables. \itemize{ \item \code{db_copy_to()} implements \code{\link[=copy_to.src_sql]{copy_to.src_sql()}} by calling \code{db_write_table()} (which calls \code{\link[DBI:dbWriteTable]{DBI::dbWriteTable()}}) to transfer the data, then optionally adds indexes (via \code{\link[=sql_table_index]{sql_table_index()}}) and analyses (via \code{\link[=sql_table_analyze]{sql_table_analyze()}}). \item \code{db_compute()} implements \code{\link[=compute.tbl_sql]{compute.tbl_sql()}} by calling \code{\link[=sql_query_save]{sql_query_save()}} to create the table, then optionally adds indexes (via \code{\link[=sql_table_index]{sql_table_index()}}) and analyses (via \code{\link[=sql_table_analyze]{sql_table_analyze()}}). \item \code{db_collect()} implements \code{\link[=collect.tbl_sql]{collect.tbl_sql()}} using \code{\link[DBI:dbSendQuery]{DBI::dbSendQuery()}} and \code{\link[DBI:dbFetch]{DBI::dbFetch()}}. \item \code{db_table_temporary()} is used for databases that have special naming schemes for temporary tables (e.g. SQL server and SAP HANA require temporary tables to start with \verb{#}) } } \seealso{ Other generic: \code{\link{db-sql}}, \code{\link{db_connection_describe}()}, \code{\link{sql_escape_logical}()} } \concept{generic} \keyword{internal} dbplyr/man/distinct.tbl_lazy.Rd0000644000176200001440000000225314015732330016250 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-distinct.R \name{distinct.tbl_lazy} \alias{distinct.tbl_lazy} \title{Subset distinct/unique rows} \usage{ \method{distinct}{tbl_lazy}(.data, ..., .keep_all = FALSE) } \arguments{ \item{.data}{A lazy data frame backed by a database query.} \item{...}{<\code{\link[dplyr:dplyr_data_masking]{data-masking}}> Variables, or functions of variables. Use \code{\link[dplyr:desc]{desc()}} to sort a variable in descending order.} \item{.keep_all}{If \code{TRUE}, keep all variables in \code{.data}. If a combination of \code{...} is not distinct, this keeps the first row of values.} } \value{ Another \code{tbl_lazy}. Use \code{\link[=show_query]{show_query()}} to see the generated query, and use \code{\link[=collect.tbl_sql]{collect()}} to execute the query and return data to R. } \description{ This is a method for the dplyr \code{\link[=distinct]{distinct()}} generic. It adds the \code{DISTINCT} clause to the SQL query. } \examples{ library(dplyr, warn.conflicts = FALSE) db <- memdb_frame(x = c(1, 1, 2, 2), y = c(1, 2, 1, 1)) db \%>\% distinct() \%>\% show_query() db \%>\% distinct(x) \%>\% show_query() } dbplyr/man/remote_name.Rd0000644000176200001440000000212113415745770015114 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/remote.R \name{remote_name} \alias{remote_name} \alias{remote_src} \alias{remote_con} \alias{remote_query} \alias{remote_query_plan} \title{Metadata about a remote table} \usage{ remote_name(x) remote_src(x) remote_con(x) remote_query(x) remote_query_plan(x) } \arguments{ \item{x}{Remote table, currently must be a \link{tbl_sql}.} } \value{ The value, or \code{NULL} if not remote table, or not applicable. For example, computed queries do not have a "name" } \description{ \code{remote_name()} gives the name remote table, or \code{NULL} if it's a query. \code{remote_query()} gives the text of the query, and \code{remote_query_plan()} the query plan (as computed by the remote database). \code{remote_src()} and \code{remote_con()} give the dplyr source and DBI connection respectively. } \examples{ mf <- memdb_frame(x = 1:5, y = 5:1, .name = "blorp") remote_name(mf) remote_src(mf) remote_con(mf) remote_query(mf) mf2 <- dplyr::filter(mf, x > 3) remote_name(mf2) remote_src(mf2) remote_con(mf2) remote_query(mf2) } dbplyr/man/join.tbl_sql.Rd0000644000176200001440000001271614002647450015217 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-joins.R \name{join.tbl_sql} \alias{join.tbl_sql} \alias{inner_join.tbl_lazy} \alias{left_join.tbl_lazy} \alias{right_join.tbl_lazy} \alias{full_join.tbl_lazy} \alias{semi_join.tbl_lazy} \alias{anti_join.tbl_lazy} \title{Join SQL tables} \usage{ \method{inner_join}{tbl_lazy}( x, y, by = NULL, copy = FALSE, suffix = NULL, auto_index = FALSE, ..., sql_on = NULL, na_matches = c("never", "na") ) \method{left_join}{tbl_lazy}( x, y, by = NULL, copy = FALSE, suffix = NULL, auto_index = FALSE, ..., sql_on = NULL, na_matches = c("never", "na") ) \method{right_join}{tbl_lazy}( x, y, by = NULL, copy = FALSE, suffix = NULL, auto_index = FALSE, ..., sql_on = NULL, na_matches = c("never", "na") ) \method{full_join}{tbl_lazy}( x, y, by = NULL, copy = FALSE, suffix = NULL, auto_index = FALSE, ..., sql_on = NULL, na_matches = c("never", "na") ) \method{semi_join}{tbl_lazy}( x, y, by = NULL, copy = FALSE, auto_index = FALSE, ..., sql_on = NULL, na_matches = c("never", "na") ) \method{anti_join}{tbl_lazy}( x, y, by = NULL, copy = FALSE, auto_index = FALSE, ..., sql_on = NULL, na_matches = c("never", "na") ) } \arguments{ \item{x, y}{A pair of lazy data frames backed by database queries.} \item{by}{A character vector of variables to join by. If \code{NULL}, the default, \verb{*_join()} will perform a natural join, using all variables in common across \code{x} and \code{y}. A message lists the variables so that you can check they're correct; suppress the message by supplying \code{by} explicitly. To join by different variables on \code{x} and \code{y}, use a named vector. For example, \code{by = c("a" = "b")} will match \code{x$a} to \code{y$b}. To join by multiple variables, use a vector with length > 1. For example, \code{by = c("a", "b")} will match \code{x$a} to \code{y$a} and \code{x$b} to \code{y$b}. Use a named vector to match different variables in \code{x} and \code{y}. For example, \code{by = c("a" = "b", "c" = "d")} will match \code{x$a} to \code{y$b} and \code{x$c} to \code{y$d}. To perform a cross-join, generating all combinations of \code{x} and \code{y}, use \code{by = character()}.} \item{copy}{If \code{x} and \code{y} are not from the same data source, and \code{copy} is \code{TRUE}, then \code{y} will be copied into a temporary table in same database as \code{x}. \verb{*_join()} will automatically run \code{ANALYZE} on the created table in the hope that this will make you queries as efficient as possible by giving more data to the query planner. This allows you to join tables across srcs, but it's potentially expensive operation so you must opt into it.} \item{suffix}{If there are non-joined duplicate variables in \code{x} and \code{y}, these suffixes will be added to the output to disambiguate them. Should be a character vector of length 2.} \item{auto_index}{if \code{copy} is \code{TRUE}, automatically create indices for the variables in \code{by}. This may speed up the join if there are matching indexes in \code{x}.} \item{...}{Other parameters passed onto methods.} \item{sql_on}{A custom join predicate as an SQL expression. Usually joins use column equality, but you can perform more complex queries by supply \code{sql_on} which should be a SQL expression that uses \code{LHS} and \code{RHS} aliases to refer to the left-hand side or right-hand side of the join respectively.} \item{na_matches}{Should NA (NULL) values match one another? The default, "never", is how databases usually work. \code{"na"} makes the joins behave like the dplyr join functions, \code{\link[=merge]{merge()}}, \code{\link[=match]{match()}}, and \code{\%in\%}.} } \value{ Another \code{tbl_lazy}. Use \code{\link[=show_query]{show_query()}} to see the generated query, and use \code{\link[=collect.tbl_sql]{collect()}} to execute the query and return data to R. } \description{ These are methods for the dplyr \link{join} generics. They are translated to the following SQL queries: \itemize{ \item \code{inner_join(x, y)}: \verb{SELECT * FROM x JOIN y ON x.a = y.a} \item \code{left_join(x, y)}: \verb{SELECT * FROM x LEFT JOIN y ON x.a = y.a} \item \code{right_join(x, y)}: \verb{SELECT * FROM x RIGHT JOIN y ON x.a = y.a} \item \code{full_join(x, y)}: \verb{SELECT * FROM x FULL JOIN y ON x.a = y.a} \item \code{semi_join(x, y)}: \verb{SELECT * FROM x WHERE EXISTS (SELECT 1 FROM y WHERE x.a = y.a)} \item \code{anti_join(x, y)}: \verb{SELECT * FROM x WHERE NOT EXISTS (SELECT 1 FROM y WHERE x.a = y.a)} } } \examples{ library(dplyr, warn.conflicts = FALSE) band_db <- tbl_memdb(dplyr::band_members) instrument_db <- tbl_memdb(dplyr::band_instruments) band_db \%>\% left_join(instrument_db) \%>\% show_query() # Can join with local data frames by setting copy = TRUE band_db \%>\% left_join(dplyr::band_instruments, copy = TRUE) # Unlike R, joins in SQL don't usually match NAs (NULLs) db <- memdb_frame(x = c(1, 2, NA)) label <- memdb_frame(x = c(1, NA), label = c("one", "missing")) db \%>\% left_join(label, by = "x") # But you can activate R's usual behaviour with the na_matches argument db \%>\% left_join(label, by = "x", na_matches = "na") # By default, joins are equijoins, but you can use `sql_on` to # express richer relationships db1 <- memdb_frame(x = 1:5) db2 <- memdb_frame(x = 1:3, y = letters[1:3]) db1 \%>\% left_join(db2) \%>\% show_query() db1 \%>\% left_join(db2, sql_on = "LHS.x < RHS.x") \%>\% show_query() } dbplyr/man/tbl_lazy.Rd0000644000176200001440000000124714002647450014436 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tbl-lazy.R \name{tbl_lazy} \alias{tbl_lazy} \alias{lazy_frame} \title{Create a local lazy tibble} \usage{ tbl_lazy(df, con = NULL, src = NULL) lazy_frame(..., con = NULL, src = NULL) } \description{ These functions are useful for testing SQL generation without having to have an active database connection. See \code{\link[=simulate_dbi]{simulate_dbi()}} for a list available database simulations. } \examples{ library(dplyr) df <- data.frame(x = 1, y = 2) df_sqlite <- tbl_lazy(df, con = simulate_sqlite()) df_sqlite \%>\% summarise(x = sd(x, na.rm = TRUE)) \%>\% show_query() } \keyword{internal} dbplyr/man/lahman.Rd0000644000176200001440000000325514002647450014057 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/data-lahman.R \name{lahman} \alias{lahman} \alias{lahman_sqlite} \alias{lahman_postgres} \alias{lahman_mysql} \alias{copy_lahman} \alias{has_lahman} \alias{lahman_srcs} \title{Cache and retrieve an \code{src_sqlite} of the Lahman baseball database.} \usage{ lahman_sqlite(path = NULL) lahman_postgres(dbname = "lahman", host = "localhost", ...) lahman_mysql(dbname = "lahman", ...) copy_lahman(con, ...) has_lahman(type, ...) lahman_srcs(..., quiet = NULL) } \arguments{ \item{...}{Other arguments passed to \code{src} on first load. For MySQL and PostgreSQL, the defaults assume you have a local server with \code{lahman} database already created. For \code{lahman_srcs()}, character vector of names giving srcs to generate.} \item{type}{src type.} \item{quiet}{if \code{TRUE}, suppress messages about databases failing to connect.} } \description{ This creates an interesting database using data from the Lahman baseball data source, provided by Sean Lahman at \url{http://www.seanlahman.com/baseball-archive/statistics/}, and made easily available in R through the \pkg{Lahman} package by Michael Friendly, Dennis Murphy and Martin Monkman. See the documentation for that package for documentation of the individual tables. } \examples{ # Connect to a local sqlite database, if already created \donttest{ library(dplyr) if (has_lahman("sqlite")) { lahman_sqlite() batting <- tbl(lahman_sqlite(), "Batting") batting } # Connect to a local postgres database with lahman database, if available if (has_lahman("postgres")) { lahman_postgres() batting <- tbl(lahman_postgres(), "Batting") } } } \keyword{internal} dbplyr/man/src_dbi.Rd0000644000176200001440000000175114015732330014217 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/src_dbi.R \name{src_dbi} \alias{src_dbi} \title{Database src} \usage{ src_dbi(con, auto_disconnect = FALSE) } \arguments{ \item{con}{An object that inherits from \link[DBI:DBIConnection-class]{DBI::DBIConnection}, typically generated by \link[DBI:dbConnect]{DBI::dbConnect}} \item{auto_disconnect}{Should the connection be automatically closed when the src is deleted? Set to \code{TRUE} if you initialize the connection the call to \code{src_dbi()}. Pass \code{NA} to auto-disconnect but print a message when this happens.} } \value{ An S3 object with class \code{src_dbi}, \code{src_sql}, \code{src}. } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}} Since can generate a \code{tbl()} directly from a DBI connection we no longer recommend using \code{src_dbi()}. } \keyword{internal} dbplyr/man/sql_variant.Rd0000644000176200001440000000732714006567571015157 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/translate-sql-string.R, % R/translate-sql-paste.R, R/translate-sql-helpers.R, R/backend-.R, % R/backend-odbc.R \docType{data} \name{sql_substr} \alias{sql_substr} \alias{sql_str_sub} \alias{sql_paste} \alias{sql_paste_infix} \alias{sql_variant} \alias{sql_translator} \alias{sql_infix} \alias{sql_prefix} \alias{sql_aggregate} \alias{sql_aggregate_2} \alias{sql_aggregate_n} \alias{sql_not_supported} \alias{sql_cast} \alias{sql_try_cast} \alias{sql_log} \alias{sql_cot} \alias{base_scalar} \alias{base_agg} \alias{base_win} \alias{base_no_win} \alias{base_odbc_scalar} \alias{base_odbc_agg} \alias{base_odbc_win} \title{Create an sql translator} \usage{ sql_substr(f = "SUBSTR") sql_str_sub(subset_f = "SUBSTR", length_f = "LENGTH", optional_length = TRUE) sql_paste(default_sep, f = "CONCAT_WS") sql_paste_infix(default_sep, op, cast) sql_variant( scalar = sql_translator(), aggregate = sql_translator(), window = sql_translator() ) sql_translator(..., .funs = list(), .parent = new.env(parent = emptyenv())) sql_infix(f, pad = TRUE) sql_prefix(f, n = NULL) sql_aggregate(f, f_r = f) sql_aggregate_2(f) sql_aggregate_n(f, f_r = f) sql_not_supported(f) sql_cast(type) sql_try_cast(type) sql_log() sql_cot() base_scalar base_agg base_win base_no_win base_odbc_scalar base_odbc_agg base_odbc_win } \arguments{ \item{f}{the name of the sql function as a string} \item{scalar, aggregate, window}{The three families of functions than an SQL variant can supply.} \item{..., .funs}{named functions, used to add custom converters from standard R functions to sql functions. Specify individually in \code{...}, or provide a list of \code{.funs}} \item{.parent}{the sql variant that this variant should inherit from. Defaults to \code{base_agg} which provides a standard set of mappings for the most common operators and functions.} \item{pad}{If \code{TRUE}, the default, pad the infix operator with spaces.} \item{n}{for \code{sql_infix()}, an optional number of arguments to expect. Will signal error if not correct.} \item{f_r}{the name of the r function being translated as a string} } \description{ When creating a package that maps to a new SQL based src, you'll often want to provide some additional mappings from common R commands to the commands that your tbl provides. These three functions make that easy. } \section{Helper functions}{ \code{sql_infix()} and \code{sql_prefix()} create default SQL infix and prefix functions given the name of the SQL function. They don't perform any input checking, but do correctly escape their input, and are useful for quickly providing default wrappers for a new SQL variant. } \examples{ # An example of adding some mappings for the statistical functions that # postgresql provides: http://bit.ly/K5EdTn postgres_agg <- sql_translator(.parent = base_agg, cor = sql_aggregate_2("CORR"), cov = sql_aggregate_2("COVAR_SAMP"), sd = sql_aggregate("STDDEV_SAMP", "sd"), var = sql_aggregate("VAR_SAMP", "var") ) # Next we have to simulate a connection that uses this variant con <- simulate_dbi("TestCon") sql_translation.TestCon <- function(x) { sql_variant( base_scalar, postgres_agg, base_no_win ) } translate_sql(cor(x, y), con = con, window = FALSE) translate_sql(sd(income / years), con = con, window = FALSE) # Any functions not explicitly listed in the converter will be translated # to sql as is, so you don't need to convert all functions. translate_sql(regr_intercept(y, x), con = con) } \seealso{ \code{\link[=win_over]{win_over()}} for helper functions for window functions. \code{\link[=sql]{sql()}} for an example of a more customised sql conversion function. } \keyword{datasets} \keyword{internal} dbplyr/man/fill.tbl_lazy.Rd0000644000176200001440000000330014004012136015340 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-fill.R \name{fill.tbl_lazy} \alias{fill.tbl_lazy} \title{Fill in missing values with previous or next value} \usage{ fill.tbl_lazy(.data, ..., .direction = c("down", "up")) } \arguments{ \item{.data}{A lazy data frame backed by a database query.} \item{...}{Columns to fill.} \item{.direction}{Direction in which to fill missing values. Currently either "down" (the default) or "up". Note that "up" does not work when \code{.data} is sorted by non-numeric columns. As a workaround revert the order yourself beforehand; for example replace \code{arrange(x, desc(y))} by \code{arrange(desc(x), y)}.} } \description{ Fill in missing values with previous or next value } \examples{ squirrels <- tibble::tribble( ~group, ~name, ~role, ~n_squirrels, ~ n_squirrels2, 1, "Sam", "Observer", NA, 1, 1, "Mara", "Scorekeeper", 8, NA, 1, "Jesse", "Observer", NA, NA, 1, "Tom", "Observer", NA, 4, 2, "Mike", "Observer", NA, NA, 2, "Rachael", "Observer", NA, 6, 2, "Sydekea", "Scorekeeper", 14, NA, 2, "Gabriela", "Observer", NA, NA, 3, "Derrick", "Observer", NA, NA, 3, "Kara", "Scorekeeper", 9, 10, 3, "Emily", "Observer", NA, NA, 3, "Danielle", "Observer", NA, NA ) squirrels$id <- 1:12 if (require("tidyr", quietly = TRUE)) { tbl_memdb(squirrels) \%>\% window_order(id) \%>\% tidyr::fill( n_squirrels, n_squirrels2, ) } } dbplyr/man/expand.tbl_lazy.Rd0000644000176200001440000000417414004012136015703 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-expand.R \name{expand.tbl_lazy} \alias{expand.tbl_lazy} \title{Expand SQL tables to include all possible combinations of values} \usage{ expand.tbl_lazy(data, ..., .name_repair = "check_unique") } \arguments{ \item{data}{A lazy data frame backed by a database query.} \item{...}{Specification of columns to expand. See \link[tidyr:expand]{tidyr::expand} for more details.} \item{.name_repair}{Treatment of problematic column names: \itemize{ \item \code{"minimal"}: No name repair or checks, beyond basic existence, \item \code{"unique"}: Make sure names are unique and not empty, \item \code{"check_unique"}: (default value), no name repair, but check they are \code{unique}, \item \code{"universal"}: Make the names \code{unique} and syntactic \item a function: apply custom name repair (e.g., \code{.name_repair = make.names} for names in the style of base R). \item A purrr-style anonymous function, see \code{\link[rlang:as_function]{rlang::as_function()}} } This argument is passed on as \code{repair} to \code{\link[vctrs:vec_as_names]{vctrs::vec_as_names()}}. See there for more details on these terms and the strategies used to enforce them.} } \value{ Another \code{tbl_lazy}. Use \code{\link[=show_query]{show_query()}} to see the generated query, and use \code{\link[=collect.tbl_sql]{collect()}} to execute the query and return data to R. } \description{ This is a method for the \link[tidyr:expand]{tidyr::expand} generics. It doesn't sort the result explicitly, so the order might be different to what \code{expand()} returns for data frames. } \examples{ if (require("tidyr", quietly = TRUE)) { fruits <- memdb_frame( type = c("apple", "orange", "apple", "orange", "orange", "orange"), year = c(2010, 2010, 2012, 2010, 2010, 2012), size = c("XS", "S", "M", "S", "S", "M"), weights = rnorm(6) ) # All possible combinations --------------------------------------- fruits \%>\% expand(type) fruits \%>\% expand(type, size) # Only combinations that already appear in the data --------------- fruits \%>\% expand(nesting(type, size)) } } dbplyr/man/summarise.tbl_lazy.Rd0000644000176200001440000000334314015732330016435 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-summarise.R \name{summarise.tbl_lazy} \alias{summarise.tbl_lazy} \title{Summarise each group to one row} \usage{ \method{summarise}{tbl_lazy}(.data, ..., .groups = NULL) } \arguments{ \item{.data}{A lazy data frame backed by a database query.} \item{...}{<\code{\link[dplyr:dplyr_data_masking]{data-masking}}> Variables, or functions of variables. Use \code{\link[dplyr:desc]{desc()}} to sort a variable in descending order.} \item{.groups}{\Sexpr[results=rd]{lifecycle::badge("experimental")} Grouping structure of the result. \itemize{ \item "drop_last": dropping the last level of grouping. This was the only supported option before version 1.0.0. \item "drop": All levels of grouping are dropped. \item "keep": Same grouping structure as \code{.data}. } When \code{.groups} is not specified, it defaults to "drop_last". In addition, a message informs you of that choice, unless the result is ungrouped, the option "dplyr.summarise.inform" is set to \code{FALSE}, or when \code{summarise()} is called from a function in a package.} } \value{ Another \code{tbl_lazy}. Use \code{\link[=show_query]{show_query()}} to see the generated query, and use \code{\link[=collect.tbl_sql]{collect()}} to execute the query and return data to R. } \description{ This is a method for the dplyr \code{\link[=summarise]{summarise()}} generic. It generates the \code{SELECT} clause of the SQL query, and generally needs to be combined with \code{group_by()}. } \examples{ library(dplyr, warn.conflicts = FALSE) db <- memdb_frame(g = c(1, 1, 1, 2, 2), x = c(4, 3, 6, 9, 2)) db \%>\% summarise(n()) \%>\% show_query() db \%>\% group_by(g) \%>\% summarise(n()) \%>\% show_query() } dbplyr/man/win_over.Rd0000644000176200001440000000315613734215523014452 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/translate-sql-window.R \name{win_over} \alias{win_over} \alias{win_rank} \alias{win_aggregate} \alias{win_aggregate_2} \alias{win_recycled} \alias{win_cumulative} \alias{win_absent} \alias{win_current_group} \alias{win_current_order} \alias{win_current_frame} \title{Generate SQL expression for window functions} \usage{ win_over( expr, partition = NULL, order = NULL, frame = NULL, con = sql_current_con() ) win_rank(f) win_aggregate(f) win_aggregate_2(f) win_cumulative(f) win_absent(f) win_current_group() win_current_order() win_current_frame() } \arguments{ \item{expr}{The window expression} \item{order}{Variables to order by} \item{frame}{A numeric vector of length two defining the frame.} \item{f}{The name of an sql function as a string} \item{parition}{Variables to partition over} } \description{ \code{win_over()} makes it easy to generate the window function specification. \code{win_absent()}, \code{win_rank()}, \code{win_aggregate()}, and \code{win_cumulative()} provide helpers for constructing common types of window functions. \code{win_current_group()} and \code{win_current_order()} allow you to access the grouping and order context set up by \code{\link[=group_by]{group_by()}} and \code{\link[=arrange]{arrange()}}. } \examples{ con <- simulate_dbi() win_over(sql("avg(x)"), con = con) win_over(sql("avg(x)"), "y", con = con) win_over(sql("avg(x)"), order = "y", con = con) win_over(sql("avg(x)"), order = c("x", "y"), con = con) win_over(sql("avg(x)"), frame = c(-Inf, 0), order = "y", con = con) } \keyword{internal} dbplyr/man/partial_eval.Rd0000644000176200001440000000433713734215523015267 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/partial-eval.R \name{partial_eval} \alias{partial_eval} \title{Partially evaluate an expression.} \usage{ partial_eval(call, vars = character(), env = caller_env()) } \arguments{ \item{call}{an unevaluated expression, as produced by \code{\link[=quote]{quote()}}} \item{vars}{character vector of variable names.} \item{env}{environment in which to search for local values} } \description{ This function partially evaluates an expression, using information from the tbl to determine whether names refer to local expressions or remote variables. This simplifies SQL translation because expressions don't need to carry around their environment - all relevant information is incorporated into the expression. } \section{Symbol substitution}{ \code{partial_eval()} needs to guess if you're referring to a variable on the server (remote), or in the current environment (local). It's not possible to do this 100\% perfectly. \code{partial_eval()} uses the following heuristic: \itemize{ \item If the tbl variables are known, and the symbol matches a tbl variable, then remote. \item If the symbol is defined locally, local. \item Otherwise, remote. } You can override the guesses using \code{local()} and \code{remote()} to force computation, or by using the \code{.data} and \code{.env} pronouns of tidy evaluation. } \examples{ vars <- c("year", "id") partial_eval(quote(year > 1980), vars = vars) ids <- c("ansonca01", "forceda01", "mathebo01") partial_eval(quote(id \%in\% ids), vars = vars) # cf. partial_eval(quote(id == .data$ids), vars = vars) # You can use local() or .env to disambiguate between local and remote # variables: otherwise remote is always preferred year <- 1980 partial_eval(quote(year > year), vars = vars) partial_eval(quote(year > local(year)), vars = vars) partial_eval(quote(year > .env$year), vars = vars) # Functions are always assumed to be remote. Use local to force evaluation # in R. f <- function(x) x + 1 partial_eval(quote(year > f(1980)), vars = vars) partial_eval(quote(year > local(f(1980))), vars = vars) # For testing you can also use it with the tbl omitted partial_eval(quote(1 + 2 * 3)) x <- 1 partial_eval(quote(x ^ y)) } \keyword{internal} dbplyr/man/dbplyr_uncount.Rd0000644000176200001440000000176714004012136015661 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-uncount.R \name{dbplyr_uncount} \alias{dbplyr_uncount} \title{"Uncount" a database table} \usage{ dbplyr_uncount(data, weights, .remove = TRUE, .id = NULL) } \arguments{ \item{data}{A lazy data frame backed by a database query.} \item{weights}{A vector of weights. Evaluated in the context of \code{data}; supports quasiquotation.} \item{.remove}{If \code{TRUE}, and \code{weights} is the name of a column in \code{data}, then this column is removed.} \item{.id}{Supply a string to create a new variable which gives a unique identifier for each created row.} } \description{ This is a method for the tidyr \code{uncount()} generic. It uses a temporary table, so your database user needs permissions to create one. } \examples{ df <- memdb_frame(x = c("a", "b"), n = c(1, 2)) dbplyr_uncount(df, n) dbplyr_uncount(df, n, .id = "id") # You can also use constants dbplyr_uncount(df, 2) # Or expressions dbplyr_uncount(df, 2 / n) } dbplyr/man/figures/0000755000176200001440000000000014002647450013767 5ustar liggesusersdbplyr/man/figures/lifecycle-defunct.svg0000644000176200001440000000170414002647450020077 0ustar liggesuserslifecyclelifecycledefunctdefunct dbplyr/man/figures/lifecycle-maturing.svg0000644000176200001440000000170614002647450020277 0ustar liggesuserslifecyclelifecyclematuringmaturing dbplyr/man/figures/logo.png0000644000176200001440000014052114002647450015440 0ustar liggesusersPNG  IHDRޫhgAMA a cHRMz&u0`:pQ<bKGD pHYs.#.#x?vtIME:IDIDATxw\ysΖf{'$R@E%+v%Ny:7\b˖-Gd5T!ERbI(D#ffwgvy~9-3Y,HZn9uu.} Ǐi6Mb oVx<)O<ҧzEXǑRH$X,灚̟ !.T*n61L|{[Ss?sD"4kiWrυJEQ0L<+})wD!GEJ6M>$-))! !ėjW'|r/;~رcH)q\"RP]M !B|t(w}{t:M*BUF)/I)9PwBtd2!C ԕ>;O>$xT*#򏥔BJ4 Ucv)T=N7l{キҗ aL&9Jݗs|!Hӌ梨UURwȄy!LSTβ߆0{I۷EJROB d||HRL&jjj΃;B?;w[*W,!mO}Sq4J)yLMMMOOh4villn/Db_ !NӴ~UUIG?Z[u!mcǎi4M;r'|FB$311Qб<---`6"B(4M I;>Cƣ>J:d2Tj0`˷MQ4M[qEOyy9,D!ėfkd2}gYC ø\u3@Em urrNH&7uf:))))?B|SQ8NVw|?LNN>)K)r| !bTQpDCC6/NUUS_C[UUաiG3%6F>wppಝRZZZd2-Du˪>Nw[;0fL&ao 322B:%竪*>ߏ-?BHef"#[ECe?qRdR~QJ9ja:::#H[,innr-W;[;^|ӟΖ?ZZ>!x>:::D"+})\.j_B(NUUZ|[ZK Cg2tCRߔRdQ8<WQ;pTJL1Ƽt:/^ kVRR#z(x~YJy uvvۻ$K YWWWcX/!桇p000" 2<ЂRnRiB<$s[6J144=J+), 80Zo lTUUqEgRYQ?Jf$N' ?Pj[P?})"IZIJyrB? Φqpp.o;:ɘrŊ~nCCCb1B,EQZ`0xvlT<dY,jjjp-wR*V{S_rM2'|k' .2bjllb^n)/H)[EQ{=b$cLj(Q1Mr|!H$䲝A<#5xG~&R_R̷ N<#ޞ]v̻!(ƈف۷#rQQQ]*#4fҦx3DJ p89kBsșJattP٢WJEAWFǎ{Wo <]槪#rioo_QxH.s#5`RD*ʾ$EjtRѯf*!R&SN]j ¯ьؿߏc5 %Ђnh+W088狀z^b4혪_B0JE;-ld2dr:.xfdddd~v uz d.LNNf@65>>}I>}&Ns)E >CO9Sb1TVVJeeeoM^}oos9 -*Rʻ#4d=#}C`c g<bM$&&&6ͯXÌR]] \ύtBΆ(Z כ}X@8@_ .{(7(5=Tɗ!N@n~겑5*:::nJ!iCCCBev)q)D" !_+[~!mOO~|f (455vZRϟghh(۱cӦM[lAJȪٳ,1v;>/ w[T\Ok4yn*'|zI:r%rv6q4CCC}2B-Vdl6;v}&Ǎ|]Qh[R}#4 CCC *TWWsgϞl)]wWU.GgG}>_6x뮻{|J%UUֻFZK?R^ZIz'HiSJuux޼~n0n155{wFJG=*c!ObX㷵$ϵZJ4ݓNCJ Rd~D.DmnnfΝ<ـfA,DMM 6[rafx ͋*T;{JFa&#,?a*5P^^>(k4%ٲEOYY|i !l>zvo+~n"d2 _|M|X,ZY577 455*cccLNNd?ȦY6nH}}=6lbiRJE!Hpe^/ 7;&&&|-mǩsŬH'ׂO_n >khh>M\­i0$@zR3)H$u!_bGvmoo ;v -SUә^Tn4΂ .]vqa\7͛^b+ի9r7nA2# /$Y@VMM e-Â/rY>я8o^*Y暌~J2@-i4̎miiܑlJGx6EU)C-[x)曜8q!,KFUww@rwwwƿI~m^z饬\ kQc;kslCU s.C,--5c粺h.naK̖-G !tÆ g25 +v'?I<7܆ׯb\| !N[("ZuΝ;brFGG}۶m%2|y^x۳0RaċXׯOCCh˗/ /pڵKq"HVX_ +\ >1C4dq6q Nhvڢ{ Rϟ?7m<9v|;o|n,CQo|Mtvv~/PUUE*3G'[]]͕+W ڧY HRى|tׯ /p9= Q\P[[(@ͦmllO[[[ޕB}}=wn;-2|x>a]79 Fޜ!7hz[[[+.Xk׮144&lAJ_4M;bcǎ!eK[B`CYJy(ϽEL[lDQ=MbOL?uuu޽j^{5Ο?H$t_`>UF9χtsC%ޞf;2O*'u!k 牧C@p蠯oE[?>[YYY94 )v)_ !B(/򏗕416R_ۦX2Q&&&Ͱx zٷo>ȕtwwgKzLy}h4P6Rv)))arr2lyv=;﨡a2^UU.^8.ۚ(̯{ݎ4`-RʇBȅGe!\N$ZzAӴ_nH)χnvhhhn}v>ginnwexxߟݏ6umvc;?䍍=zg9aTp[XUzY2^Dahjj*X ѹJfdddQE)eee_Bd29iSTUUթ(#J)--9]!˳֮]ˑ#GXn X,f/ƃr(//C`cV}۳x<Ͼr _Wt҂KѦ&l2']/[oݯM-ed~S|32M(Rd~4̖-NA4M;(ʗ?,Dzh>~8X ŢjvOfG{mfO+ozpPYYI__ߌ@jX,}ǎ;p:Y彪q%{s=۷,E=k椅ueZB yyWۤةqjqkQ)#{ffAs:;;鹩|lʃO"+*|ZetѣbNi;v )%hպ6JFW9vF^e zXՊlgh&X閭7|{R_7|XVD"Ys:ڵCeu>E'KŐRr^|E/22ݘTɗi DZZZ_v 188H `9`R>&7=zby5b|6_3vR7=Ec䅅3I)ěN@ ɓ'yW 466f?z^<O6 VZZ y8pիWc2r}g_5MNNorU.] 292K#SE,477r D"+[4-1az}(JVh *<H?B|n}QشSA>~8RJ#HD _|A%:YH).cN'>/KƯ*?.采67 C%E! o+M-- 2:V{Qڇ%+{' Ưf嶖(Q{fB Ƨ4LxyOWSJ)}pEX,a i3/|nl6JӴ_RnoFjǐYΝ3$JA` UU6gΜ^]͞2\ ~w i0mmmrnQ$F5YXBcj{8 v ԆONNݍj,̃)4cfρok,?Kcǎ\{0% ̦M8|07#FWH<&9V@[VQsf$#{$+ ڹ!=/Xψ/w2Z!H@ x/us އϡ;Ը3rg;oRzGVT@$acF[)S.#heN_͆rOL!OcXbsl?޾$ЊApanݚ &M ÌPSS3"kvLQ"Ԕ- lE!"L;X} c,i~-}z9[;{?0ed~32?;/1 P@)/MWORdv%6-V ]!׳BW|ErZ[[gԆ SZ:HF5:::tTB,tga)d2-!WM] +=o6lقs$;š @r@dl9MyDȍꫯrɼ:c 9f4|";o12?C Q_PPAt7ğ^J|W͐)&<ӑX1reG̶aiҧ*>/o(B<'PRRPEWWc&!4o6-n$?xs&388իr=3ƛL/ Ü:u^zyEӴ͹baU4{>4Gw5ᓤe춒I$'a•M#]JÈVXM&َ+MԸԬH~|=@ֻaF%[?|Α.^~`0}0m6oxq;LwKy cDkgH7Jzj0Y& Œ8L*lԺfd~***,VZ7.~o)^7pr[>RUr4 B0_\\Km@$Qs:2R"R\0$F 388H"b /3g3$H'N̈RVgt\P____vx٭ͭs&Z9s.џ-\.IJO-644f͚ltrxHnW6E~KKKYv-ddk׮k-9W̥Kfr'_L98|K9͘rpG88ldؘV8RJ&&&H|Pe #Ԕs9mw###dK!}}9|pc`_nڿ<ﭪƒ#z?6K¯"wd~eFkuvv欠[( KŊVY* Í^ʚ5k8rׯ*~>m N58 =Ƭ̯=:Ɵe~ܑ-7 _kk+9kSTJ-Wnؘ7f^c| l)M"2ޒ h4;hZz)))ܹ5#immm ;w׶&3o|";oܑd~(hmm͊T.dًU| GIˍW=O%,h}cΘUd,yWf [4f@ )+|χ[*Id0|*#;}`۳9d~F~vgzP-|/bqݯtuN7+>8X]RUUm6go'N(c nT`C4 !D) UcofrH =?"@U5zi^FՆ/Ԥ$[̑+f[bu1R^E#pY!j?LNU4 /)! &n<kkk+ܚz-܍ضZWCu5z@ RwSC?~MÔI =ˬ&i)qcVQ >Pe5f&} 5+;%{c8IꮢXQ4U,VoNK Դ3:.a1 S΍իyED{v삲rK d,H}a&$KvJ1Gc($POnldx\/WF1[ڙR&Z:Fl*wIMx0 iRD ¬e,?az(ɓ曅Lo!'l6 NT) 奌Wx޼o0 `9uO1<2v'(lld8_k\C='P9۲LQb\3F()伊C`-I{ʺtαBH߳ `ζNCߢT#) w >vgg'!uaX!Z-)IL 71PXBʦ' tԗ09s!>}F ˾,G&ޣWX#Lh<6F/Nʹui0oC/lc4r,D"L&ػ ܫ w4/&ф~̵wܐ~D͗(</F;…Ŝ[HS^swӍ,o99Qj(IB$"phnA|ǡL_*/ Zm5G"]]>?())1 Zf_!})XbȢ!οyLfFѻ!ka4R\zVc2\?9ࡏ!n}O]GbP'(b_-9_k𩩩l~e~,R`! 9MMAf܆E/LĆIĆ {\JI+67xFįnuL&%p.goF@~k K|qږ<`~6Jy:lk9s+^ -Iӝ|;B[gAE Ny2}Zjw~Yo? B*󛯘XȲ/rLc8_.8'zUQX8Th8_R|O\Q>8޴?HwaAG`a3Qxe[rYYP;P&,RtݽpsP/`- / fBO`U"s떙T&zAF:MZĿOXFvK1紘 l0ݲ)˗}F f{{{v6u1'p]=#c 6 '.BMc8]EgEEE[! Y^,C,W ?wں XMMJԿd{ 8'Ʉ#߆%P_{{{&}ŔgٗC Q__sXxXUfD,PT> < :|qJS(r^Ӡq?no#,Fo_d~1aqb*̇-MNew `$3_9 B6˹]e$m^YDD^N#+bdOGيAww#+#?t\Y_p)2?EQ.2d2mmm+2}A`ZFԡiiF\o+H$г%©KoOAYPY}LPQvs$4hg/-ΏBEQn̯X#^ O;3o0N1M#*+J:#$i$+%qL:|$,.JG?[Gu27Z{v _8qKuttlҷnKsvynjޖ(ֳQj\waVi~/s=CƓvGhXS|?kxE?]FMJCȧ~~hl[t 8,%lX ( oR` soŤ阮Zł\0-%1'|Y44Uդ{ճ~?}.ۯCS3iExw,/_k9{B0gSb1RۆM0eVTT(W ԶAr3)*ZV1Z#~PQNM1iDy c !_{5ǓO!!FĿ-K47E`%2 ZZZ lPL]iKjpzR"[Ѿ\%azڪT*EGGl޼9'jnZkcY]&7jL iMm@&0$-aHJd&]d M,َ<=mEi! ,߅OL~zdY[nGdB4~]/ZT:-Xn_>|m]Z mJC2SR!aO3.zL1v2v2fҙL`.#me5fd]> X Z?}tu0F#ߴBS ]NtBM"Shɜ{00vb[E.[y_'woGl٤pJ\E9'tK7Rg̙eφY2k ^8Pb.Q@CfFv:Kzps++D")v=vdzI1%٫)QG>D ƫ%iv{@JH'ubWy7uykV`#<L|0|կ QI NaI<'oNK槥%8'QZ VT..ʜrrY٨výᑭ}P_g貐Ӿ݊o{p?_D8 .,j ο) F?#HIk51j%cVaG|vXhP-?Z# @bMvXvg+L&ӌ+vl^uZbyY¥*سqؼ2i+1dƣ`lL$EQMfv1֧NCEC yG #_cQCO)a<< !$Q폠DNK 6"xƹ$*3)=m1+S.$`k9l)|n57`t2 55Z,PjBov4kಹ9 mՈC{VwN4u 96%U`v|햟.8q%^- ~+BEcCj]T~Jlnkztu>*ꢧhMyo[rBebX"`{f%X?Kɏ!g?;%sڵ9:kKݽ"E95q/WQJ,Z@;ϫ}g'yK/e-E.k[-lc{~rL"PUD* f8ƿxB?h?]4Oz.|9aSAO!X_/w:W|om;JޛVc2=WUU9RW Kiu^ EL36m& p¦>oq mw(/>wSpc,V1 GT;pW&(ci5 ʶ{4Fde~#I>1J8QGv$oa;V2e]k3?,#d5 N|a-iɓÉ?mX,qA:bek3hkD C!^ [㧈44q:Y 4 lLw8aĵXnXJ6z2o.I躵W(/XLl%A+ 8%ITDKC_?4`o/ Wشt3J+Ԍ:vRae^kV?{<W:}kQk@E7xJT}vwyɺK$<ɕ+WfZ~+;z&Ns" #Q|U>nh4ŋaΝ9\K f3b}uun*$Z"`Ρo5_pP(,mdPɱ$ޚ8N0ao;E_5fs&gZòy+\+ <U^ L,3"Ǻ_WFyr'iAjKKa{'9^ L[S[[itvvf_Vk \ZDD, %3,r wEpP@ 6I$tLee8`&:aU*$*}SIZJ%J(d~[,ni$V 2-Uk'~S\j ݻ_z7UVtD;y)pswQ kׯŒ-ڸ\l6r/E/HT !nhyb #-^HgQH/EAy${PYz"b ;٘ eI*AzAG-INd"A3$n_{I3ݟjIxv%|1sj [pZ{PPN[HEoQhz{NA&T2d ^>+ B\tikghljko.$x لЗi 08wf_Sԓl0,H:rD@UF?ŐX`\[ D~|pDL%IyI`Xyni:%8˒8=)bppfJ{ nY^;焹5RRɾ4/]+'^`05DEeYYI,3tF@k *J k$I(j%V+2Z [8r W^G;{hjpֽ:-M ny;0$E8?B7ARpH* )Db Y[?kU%TyE*.~vC_`mC:V ]T|=2 1 :09$ A*I3Nec4U>RqQ5+}+`Uu)P(euk x4?Ձ(ڍIs Ѧ˺)HELzg)N>P/"IO=rbDH\jˡ ˹|V0Z$n0)5#u_ _fn7y/ɃXHd^ |{Y?'a~u:|@`)ˍՈ3Ռp#3DtoRADrB/@r,-ӏ=K9.O8pE6,5hlL ~n2"+PNd`*t!v!ѭb4 /_' m7KuYH]56%Fv#)siy=EJܑIz^<șw'P'EBw*+g:([R_*g'MJ=-SO>t:AU %wK4%Z(8^IxD|DRdk#d~팍e&&VfZ錅ᵫ$tAJ1Q[SMVE{1!U'I\1îrBB<,NQ6G/]jOL_E v/DBp\S5ȴAKà /7N/RN;Зԁ. #_`K wjILY_# 5{ӔFW p (y Ѥ!S]] rh~+"RPx @!Dsŀ9̽mcr&k Qߍ6uv^D:a Ʋءc֭Bluz%s` ,)adzfׅ ROi(8^G"QC% $q |O,of`*Qgc ^+gS0y www&Ɨoec׾CnG~Kޅ&9ٮkp\l^7S;M$u bs78x@A)B av %(.&,%$ }] t2vL', <|T̚/)%|NS~Bڸӗ뮅n;!=XشNoHP֌tתi{5WU"̴R ZAAt&u,|VԠy5ozgK^j7BhcI$X,444da\2߳E2-eK.}SCBt3PcH)S* 4w[lg^"p6Ma;n=b $0:0{CKȹSUɓpΖ}ɏ۷ < ;!4|uq]]S相dmև%2>DF%r=}[#H% ԃ_T19K^#-4<<׋61~vgKwW*[O`#'t򪪊cժUH$hkklGhwۘLSēzr0~":~a׋LJ:MbtKe֭LXA?^ |Sj3]Rds.+hRfKZ#'8>MiBЄG wކ+q=o(p2;=Ecȳ=cknt%I+&.((esGl0#9!ԗB\z1X_09{AU5D%^C/ 0< kagލr)G٫NVmI ioog``  \8佗{KF7ETQm~@MY,D|7Û 7lt!QBw+'9G>O\(ÛUVϿ~>*aބ}{t= '/?fhk9?VAæ 6W\sY(R/LMOXḷavp:xmq((x%m~z8X+<h-ҥK3tAC{Tb'sv/O0vQQY"!F!y6hm[fS%ɤfqzAMokiiɹ}N , Q}UGõy|mEAs{])O(!`t?`zNvo-14zHLh* RJD O_z^73|mMCT`s3{c?ɘ5ٴj~3PT3 v́S җR!H[l)ޕѫ.@.i}'v5YrZ o6z2h*/nf -ɅE^ĵ5%3e~=ݴwYkT}THK;(ƪFPFW\P9 jl6[=t0?yA'0fn}Pwꕥh4^Dy"MwlZSM ᴓHZ-h31`drQ`IH$"QFh:p+ˣ'_jBkGj PT !`j9c= :/*lJ#k]iJ`]$0_ RB'q(mHa.0s 9GN.a@o3Ӝc#Ptj@Bw'8=qi״܂͖[thhiibc8{'heIV9+ehxpٳq{Dy"-2Hqd~4+#pxt_KiAFN?'MäTҢf6Ʋxb䈃sx'(olMu%r;*FzXpZBuId TB X_c]FBEYfj[I:'࿣ݓuSg_r!9嚢(j*l6[&3,*}/ 3[%sol؄صg~7F~ߗ8qpsv;Y[{aßg!0$rB.))&ݳyAdjO##ږ$SԹ`OniBk>{s8o@'t%pu5G=QJp >0و<~/4-|i!٠4VهXwq,h.1jf\؉&J`oN}51XK K>I~Ƿ '<}FVZ_;$)V5܂ȖPNp8hnn|+rD-H9A9&⼛#vJ3fne5EL"D|`ǮC5]>~^ܘkRU6'=`>1q%YO:i海7 @cI+Э&m.dAT~e+x$SЭJǎu4S| P]SͪUx^"ϟ7sζ7uUQ>8M4O'Xp?Ǩ47#Hr֑%|>XT(fͪE?Z^;nc=E]ч|zQD13%[iA>8.cͫXawM8F#T\ȮLMMٙS !bbL7[}5f:hrg4i!3@ٍ/'2{݂]%1NMKtŻx=lIMM T6:;;/@EQڂG4 E3ĕ9)M# ^b qSSĶ!5Ö.^Wlq˕ɤcZ) 6llijZ\!p[tL޻JsBf)6֭/3O#ϼ+6*4MLuGH[HmD] `B%p:R!tX`061$qR&I"ħ1{}̷] 4 \ŞS'*G=]DT/^|"&ЈlfppnZF P[[fG(Ŭmg3CWSihQj֒IF~kηQeēݮeibf a'^R9.zV\{`nآ4Mouw?A"tu,9|={a𒑡3,ˌ)Ks#S::;4&8jL˓͖ h3|Yisވ5LٍNd̿Q.;LBnHSSK-{<-[Fq e~+䮶mLnܞj&Y~A0 ~GpI"x>@ʾR;CJ q~mӇIFg~/XT :ڐ/|i8w“ޝ墥\J񉉼bCJyyyv.IO$c ϱE(Śmh,qK+/eoܞ9o zD> 3*<袪[.]4cj7SQNq,B̀;OI#WѴ:P(Q]TAaꆲWGb ,X7GZسn8mЧ%*[6B}G_"2t/Ј677SRRsjb-mk:0֍*D-3xgu$,PoBE$ WA۷RW7wq2~|UxRk}VTi]MA]'#BR.cG&H$Tk$Q*QM̔&CTydO1{&)0gT,\On%WQVBAT bVi`4Ӱ~V94Z-Q=5~nZD 8N3~}; Jvo!5H7X _+6G9`fl{kُxy#iieV]e3^D?!9WL;𓧐lpMM͜p!b٬/ZZrsM@2.VYx1LBcP=OHdb8/]1j@Ss#*<=Y+I3n@3D"g;W!)bո-󶺙?[لEHNß?eOWC(| X5A^غJK*t!@ xֶg k{s5$}CCN)`rr2[$1N5%5m80^GtfY$>l2S)IAΞs)/,㎭FԶug:_'Ke' -o\B_o{OJT8@Ye3v'{L)4Uest;?~wc@űV {HƏl=`vpoᛔ2 b! ԭ3Om_M $理$gB3{u*vv ;)M3ޛ[B =U0 aClBGl'=ͯ Ozclj'-\N(㌸^cylʶs_[6nL1߃rHIxb8ZY\K N3_URNB聬 5> \Xo;F9?73O`oLc^3*>f־B¤F3d4Ű(zSN(!G݀I7% D oL7q_wrZ 2~IfzlQՋkUX,6{;H&9 TpQJ(qWa2uj.DW EC&p%{H//<|5/ڮ29a&jZ[W3Hh+0Dㄊj'i(S ߱fa0G!ɥ M$6)zA2[#]዁Ϙ*% W\-lpφzkt(jAFRv+bfz3gIxfRYGx Ƹ|kmpuMJ _a24Hiy=NWj,8[vR_SMi.Vfe8 -Dqm}`M+2%!pTvmٲ\'SQ5ɶ4W9gf)0eV% }<%99$\Y'r 83nJ7K 5ͷXF~o| %62Y=Mov%/0|:>Tv-ku|<ĥ+7܇dAT"h$HljӃ[UdF*' ||x=kg1umB̐%8\8|EhV]o[% ob|VU`먉{lUqƧb5(q e)5蟄Nɉ>hhJa-x)#y^P.SvFJx/੉fJTȂJeHKk3K?GN`8@L$" PHe՛G%<BڼD4h❺]ދ cѢ9{6ʃO[M88`Z[[z9d29ZZZ iQU5d@!K'~H6He;za 2/40Jn'D' myjW =,;v Qmdp#'+o墹z A( \H0U 32Gdr"/I_.օ*,quƫ02T!c1p|2?`^Z&L]XA,GhiweyBv_hj$#}m]ӎŔ tKp/ $}e7[%)Fw@(8㥦ښ*VyT:w̭ dt}hlEw'cɝ 9OeTUVQ N78B(r E9C`N|%)D{n(s*WT+fN3m~ƫ ߛk= QpUi~\ƞ1vIr[ 3:gA 0#oqf*0JHŪ47fl r)d+LF'he:<2X r@#C*(u{VYK Z^ l([W_ti<Jܥ 020/p# 0-ۿOX d}a>ON['>SpDccd~ sL߮ i=&*nE|\b*MVMw1M p@`ى;]v混HabTOO)P9%!-xíM$i4mct$8$cbg^Mr21j˛o^S J&9@ky ߳1BD@}eZ/Ld8('޵>q)}f p6sy)zK4NUx:EGIW\bRT*mE3=Vi._-p |AX|x}9V{hD[-^LdpC{>FjZ\L[0%nMK3g< e"B#/)Bshh>֬YF=E%o2?|2\]dO\kf kBvɅ'kR\,OJ‘0W, V]]222gߜ%;9_ǣ~q q( B4sUGH C|f v5Ěk@ 0E6G3E"8$˘@fm8{=JM1v'QĒSt ]'01BuY=eJܥe t |bzEQV^عгf, 38yd~‹)z`?鐺y:[)z\i ߞR1;+f&;r{V*"nHQ3YhlڄQEydՁQP_` ebi?yg"1TSԙlmĤep5 k,q[KxjN("T;/&BcxT{WRZVxpѡA&AR.2- Y!b%[j՜i~FEӤ>3N*z\&O$+RVBYU/ݓ)bczpX,ƕ+W֒-'%ì^6[:!,%]-U%6fmJmO(;_R\r-Zezr && Ro rlMJTTj_/;*H%j/p\ۅ2e:R*(SA<2|ZJ)-$ 86D @l*JZ8]W}pCg<TD͚$'kRtP(BP*6ّ>MN3Pj*eg/w<3U~wpWSf<]P>';O(IV79|#7^%4iլRSK7A dBu3G}` ykF`rHMUR$Q ^S_ҩK TB͇xxͿ-Tgs2?L`pԭDfMJ]Ӿv]1H0er(P"aЀdbllׯH\[8轗[=B⃼xt)Ȃ,$bY?ty _,B P;>a/olqK:[*Xc )D S!l;nG)+#ɫL,Hh6 &0:-DA %g|)N%M1KA2 _|fekxNg*TVWq kk*VS=^%(\")R*4׿`kP #QBH'eD.p*=#v6F0 #R d$rrä&e bbu%Yy"nz1z{kt:㽸a(fu 0ΩX:ϥM `&ʢe_y6op.o44rzJ3iPPVbJBR2Ӗq jIyLnrһTiOR4u\\kOަV@<^J0ԌRWUozcB\k_旯$x)RǩM}lQFs,b>9-~46D:YxLD~.WWqX4!37D n7zI{3SY=H.Ty>ɻ)&,z;1!oN!btf\՚˦EŹCރ4QpbW[D 4?=X\i &Sm)E6OKclRQ&?}&sHmnB"iBE2xcw*6Odmf(NS3E>^ g u7jU֎e1/Bl-T'?xϗExsHUAMKVS{̔TD9<S)^Or4ELխNFщm1abCDvڌBڀM13x0JnT-p{EcKMkfIgX((%K9 \"yv MIx~3LMC/5e#ҔS LUH)8e+I,0Q-s)kP2#yMzaVf?vw(IS?` 8 SQ-S,f,)X;r[c+QZmWRtIשi w+2d2I{{B*{ى!97y/q=ֆbH)i"2xhՕbiT = 88Jյ?ëߠ*^DgyͲZ  œ `j"" H,Ǫ b$%fŜIc62я/މŦi%ILLB5v@ [Yo^VW3؋QKa sn>19$0'O$H,(KRY_0s Î=Tﶰm8]j 4/'yoLk;ZW,!DtzDu۽V!'<6AKs MMM-Fj:ʿPt/H!H$ĴsW^]I2rZrhX8M JyFbhIRDw2@]*3#7] }y>:qrCqdzPkECJm~cCbPH@4K S*iI^oXvZZ\ F&`a.!U$Ѥ-t%M0M~nYrUz \{$p UTwq#]fZC _Ҕ>yWW5`5IتJņnVv&&&nt:Zyes0N3>~]V0:M \ i yKWsL֡L46`HPQCتP,I}R@d m]#h Kϕ]P"  ˲,V-r@CH$2̯Vy?by&nGA^O2\ĭv.$(6 Al:0½ v.F0g9g \{/iTPi5[%ɉk6ԍ\YѢ %fG0#Re4@[h7Ó$6dZ ex.ɂMlJ@4IDATP3bZ\O$PVVb79 9}4@` \4[\Oi T~v9wn~"ZmWg:|2?=w.^8CNE'I zBPKa c{!F" o%y+g|)jKEg fO!jD%;H,VLsҔ%GvNP skXL>\hȀaXFGG~:H{ *rmJ,V u mR8cYqvIQtCo_M+%&=2y盡(TյeiOvǓ~R 0aԌ-ΐVFcN.eOx71ȥ`df$A!UODgh; d<#[ Q<9I ttt̲,ZI;gO*Z|GknEUaMTE+%n-;cps) q= qŭ#L[ZZWq^vY[bQ`զܖ(-e]Wխνa7H;u^#e͋t> \)P1ukFQ , EZi &i 7\}lӦwS &nu|uUUsn2wyְJ=;XPVה`(ߓ|H:jK>$rhdd6FF`'rسYײ%io" k[Q.Ͽΐ6D\qfǕP=h쥻X4(@GparF3i!X-] N&w SJvlI*:p'PӫU!dbu|Dq|ѕ%n8OnViӧPgSEΖo1CRWUUS&9I&jXFiEm(0f (F)q%XXA,fpvB9JeaYVBZ8t2[IxNfY~Fir$S8:]8-T|l;ϑiO{(9ws  @$ RJb,J}{z^yg˖`9HDR"ARfHD`r=ӹ=3=3=o-.H@Wӵ뜽/'aϐvmG<+68U9/G֪ m9k9Q:=0m!esܒp":Uxԉ.MoyY:Lc\w7Ω=¥ȸ4.tA*d"ZU yElUSSfc||~ҳy&oBeLLG4 ~6uMS\[ŮYE\ e(/\d~^4Rni0=6Yv1CEc d!J8c"$)93ղ 3jW(Ӥ?ꆍp#S16Swe\c'*"XQHۇlNӼEN7yd~=JH;KعAƳźX*4M[XUմvG__gXe B$8 [yn6韋ZLqQ_vl䆡b&DW@N~<^ͨOl\#UM*puI(Op>73% ` ˥vX2q &UO]wEb~~ QTB{a B b;wΜ3%%%l۶p8D"hFoo/Ǐg۶mlذ!Nzjs-3:1?V)E,MK)S4+jG 8̠SIe \i8I/?7v9KP-laU.$$UxǴ=wh&ǘ'*ڱ"_GGǂ 0Yga%y5{YJ7/J q!wy뮻.ZZZhnnɓhF}}N2rO3wd?!u%]Œ%+ \NT&$BO~ϱ¼? FŔT;RGC8f2fd~ 0L!ӓt| q5i2s2e~v}:₂v;o6.~;v)***蠧={0>>oA2LH%UhO\-fWiS!aEe?v*4yRH]Zڠm'??GK }q6kзˎ4?F8cR25e~ _$'c+xYrL___ߒ/EQ2n$vH$$I>x n7"S0oYHN(#s.l755uZDQ*WTln~ou‰}~y./esh"f5pcg2@|AUeC7gAJzb &󳤖wƍ)/alܸoD"Agg'μY\1Zuxs3L=<}.M\/_)NP=[)W_nWx6fU/~UT4XU~u@-N[\J‰%d~TE:.ofz Tӱ W"sTTT044ƍ蠤MB~ ?lLOO;҅RlHk6kk)Omj k9]|h\KwQ'D%[y,0#8S_QMecU鰓DC.PV7 b.SוSn?ZBTV je~lTUq뭷255Pn)jjj$D1,eMMMNk!Ā(RߔR~(J@utk9d#J^6s-K8ZLk0(\v|{y3O}*]VUZJ%)嗀iӸQJQk)?͕sJvp޿qg9I^:8vMemmnm b=:u*b!!Q[=z2%]DRd4 IdcR(A1(G( ѣG9t*۷o#Gi&ttt{iY%+{ifG1zxt]g۶m>}z~0KEdm-4NT6VʹCT+k'aOw-n LZa@-D/3y~ǹ$JNOP#zڱz' ;mWZ`cTF*2vGeHEuFwʏPJYxs]!7/u/'f 022¹sk[ˆ\5A!cBݻ{gid^{I?;v!Lm"N |ҕ4j/, OH%&g.u3Thh(. d[?ߎ霥^i‚g׮]u)lUl |;Htl6TU_rrR#Y'Jǝ^B'EJy/@Ion z';a Ҥ-E5/}Zv̴)Jlks8#W/yI1!@6d$P1()o™!t欢SئW2PQ< 8L_;- u$][0q;H'3l5WQ_8F26`5SPP-[4{'Nxodxx8QWZZ熄O !x Bp\,9u_J)QUK`Z(Jr.Og{;lRRrT%2:|;9Ψxl;#.Œ{ߣ%:v2tY> PSgZ81WQ/wdlx 5u}VYh% ODYʒd~e~]ϖ^P%cTG:P'>/u޽Oإi1m`1ںW?kO!TB*(s Sg`_O ̙3qAΟ?϶m_=#=?zoֶT֪MEy0h$Y0] +zd2(R"/pq )--x׸;u^xjyz{{˪WWW/;维(t]UUEQ5fb-u}Q>xπߑR~(wuI#MQ%M6-KƝKtPW24\+my x5߈Cgr-deq\#H022Bmm-LNNԄir}<1ݞZuT!?b6Á<5RnW`&4OhQ m&~88 .l;b(X00\=)lff>JRwPqsl.ooӛS.sd~!e~jS׃bjY,h _&Yds[ph\΍7~FGGyx饗طo_ٳgimm~^~efff4likՈgB7(JN\5-BAA0{S|bSl>"' A/ QҙR6s5pCpҲ \R{j6!~|Cqn-8GJ n~,cA|5-3aHC|{Tx_PR ]| { [X0\Ng \tĪLOO}c{q7RVV /@]]SSSSD"!8"(Oʵ\'Ԣe0_R~hXY)(|Ρ;Ec/8*y1XCW TJ*I!`B"4W ;8v{ok' a,(=kэd 7I7_]JF]g- ,;S K>i}b_H7(\.n&\.&SUU]wŋ/9umu첺:R_t]RUɏ~5V.L& 5K)_J+, [ ݦʽ p%DT$g pXsXfFaS5eEwP_t3 *H3`' &.r@>IZ;ű#qEn`33liY;ꊦ"O]LCN2l0 ;yLgI)$6l ՊٗkhZ=%Ku\.Rekvi&&&&x4 ֭[ټy3o6gϞw>~ ߤBoLNN*((J\K\'0 4M~KlѾ1;Gm|Ás<+ ?#bXz**76P^4JHG3ژq JYFWTJml>onH11,U$Fv,)RP-AB@o!x 4x-ҟB{ta*, 3s,PbcL&ssNx穮6lcǸ;yGq:z<>oֵ藍-꺞XƗn;CR9nI>Ri-s-Â#H;l2\uV, QVVyؾ};MMM8N}t!*la2*d2٫i^r1Wˀ755ޞl=Y@g(\b yr#;gx$eO 8â2&I&R"\6p _НeВJ4 ~[$TH(HglK,!?`S{'tj,v6IBA~nKp3yE H v38Snj멛#[svlDZV;I,d~-|@ocjBrF~H(HX? QБDI{YM'&&h;= _6̥!EW T6UrgƲd~ E.-H{S\\O~nJss3:wy'Akk+vCCC([a|])G gq]\#~ܾ|f|9H7 j+/ g' &ԼcdssTܞR .Y6ZtO0Bj|aq^_*jabf )1@.j094H$TJ~YrTTTػw/EEED"vAOOmmmTWWp8hmmeϞ=A^x8~|N8> jfx{8 |CQ>i~[ZΣ~QpgLX@RqҒ-/xPJD0g{S>}>bueb:G> /@ !)$Adv< ,3OvU` ʃЪJgݓ5GH~;lKǯ~\^C8h3f7Y.3|N{}\7\tB===>Ϋ:+wuD"Ayy9۶mvɽiLcD"1aydz_N\%t6XVUaw-HP5:60UH2> @I 3 ІMa| (,UE&/F#@!LO@ruJlv:op#c0C!IUd< CfUD[K퀽}Be2#ϝkn~۶mϛo<.Z% &&&x뭷ҕ{[O/FW$Tfhiia޽agUU}Ɯ$!O(cOD|vvH6|}7v;l߾ Tua$Tܧ+IUɽӅ 0 Dt BtzMf UTA}OmD` r}3.lEK . "V2>;\[ f_+nkk@ ˦MضmLLLpiΟ?+:t#GpP[nUUywL&}n76l`˖-y eM?EoRʣBt%ʁ' l6f)d DL;ڜ@lxLGg5.**ʅqc.龫U7 &хD*?rfsQZב* \64Rvn> f0K){[yFDYlF^/uĊCѣG[yo~Y__d~xnsXTk&-XEMd2y0C\!PStielbϗ3d {#{481fb^H&0 S|Ejv;?"HtJ@Aז<` 27amęKnp@MgG'r(e~]Ga6D"߼\466hkkԩSע_6662!B|fH$t!ĪW][ժzS?\Jh |2nfÐLm_l&_B ^6KY7a-m "5A̯0Yѿf;qc.Aqw:l4ųB1=4gyxBYs@~2h)eVŬ0 ^2|}ŸA9;RcY(^HJ7nWDt>Aa'`AS̨dT] %(8߀8^G?~OG\~n;Y_'&&Np8pUsa2UU%[?R~EdHp@8&x>x}q#F5TS&zox~>d_6TĢCd5rRḣ"'4rZn`3l@Il#,R !)-qm;(+*cc,1.x qd~ `NJ Rʝ9 HllOB-14x P$Qi -%6YMȗtSo7ti`WMUQ(-,$jB<D %$LlS2ՓOillj2K((B\+U^O<5t]f{0/_R.f\BxjqiqZ0x%JPl,ч#aTl'ŐT1$bi7\I%LGccra˦́盳G2Owf]HL1!::.4d-})wD"gyn=$llRyH8(NBӺu+1cMKwξ[-u$`7c&"*RRS  *s8#8srBWUU_Ո:,"]$[ Tgq%]ĝڒǃD Cw |_gx*,d"w P}xǾ c<כgo/ HDti{mN+jg[h*Zٳر]u)9EQ\n5 =-3nĈ=)w}ELn2]Bjs1c^`zduBVaF\6VIZn"'immezz:}}XMO?6N[S$kz^6n5\u(R^\\5+43p&,6Wٔ-~dfj\tUNGl9xNFg0mA1x۔2yyyiFJpN^dw8Բig [~=O.H?x;fEU7$W!ķDfk_`-NFhw"&P&輯6%k K* >B%^?$VaM^IBU*A/wUBb(7P{F6(E#S-LGVZo$0,*٨ϷWjVM=)y![sܣ*RMSHhZ 8CROlѐH`lb|,%TSajgcB'RYMVUV Z2?UU~%tRK`lB^r-i$rJln\ R9jeJ5!ēa(T^,b[fel~2;4,ڨ*CR?-Z\E102|`/^o}?z_eN)➔l>r-f`ZONN2rD"a4G}r:VvJ-~DJgLr8b_z7 E%tEXtdate:create2020-07-31T09:58:15-05:00!P%tEXtdate:modify2020-07-31T09:58:15-05:00P EXIENDB`dbplyr/man/figures/lifecycle-archived.svg0000644000176200001440000000170714002647450020237 0ustar liggesusers lifecyclelifecyclearchivedarchived dbplyr/man/figures/lifecycle-soft-deprecated.svg0000644000176200001440000000172614002647450021524 0ustar liggesuserslifecyclelifecyclesoft-deprecatedsoft-deprecated dbplyr/man/figures/lifecycle-questioning.svg0000644000176200001440000000171414002647450021015 0ustar liggesuserslifecyclelifecyclequestioningquestioning dbplyr/man/figures/lifecycle-superseded.svg0000644000176200001440000000171314002647450020612 0ustar liggesusers lifecyclelifecyclesupersededsuperseded dbplyr/man/figures/lifecycle-stable.svg0000644000176200001440000000167414002647450017727 0ustar liggesuserslifecyclelifecyclestablestable dbplyr/man/figures/lifecycle-experimental.svg0000644000176200001440000000171614002647450021147 0ustar liggesuserslifecyclelifecycleexperimentalexperimental dbplyr/man/figures/lifecycle-deprecated.svg0000644000176200001440000000171214002647450020546 0ustar liggesuserslifecyclelifecycledeprecateddeprecated dbplyr/man/intersect.tbl_lazy.Rd0000644000176200001440000000274714002647450016443 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-set-ops.R \name{intersect.tbl_lazy} \alias{intersect.tbl_lazy} \alias{union.tbl_lazy} \alias{union_all.tbl_lazy} \alias{setdiff.tbl_lazy} \title{SQL set operations} \usage{ \method{intersect}{tbl_lazy}(x, y, copy = FALSE, ..., all = FALSE) \method{union}{tbl_lazy}(x, y, copy = FALSE, ..., all = FALSE) \method{union_all}{tbl_lazy}(x, y, copy = FALSE, ...) \method{setdiff}{tbl_lazy}(x, y, copy = FALSE, ..., all = FALSE) } \arguments{ \item{x}{A pair of lazy data frames backed by database queries.} \item{y}{A pair of lazy data frames backed by database queries.} \item{copy}{If \code{x} and \code{y} are not from the same data source, and \code{copy} is \code{TRUE}, then \code{y} will be copied into a temporary table in same database as \code{x}. \verb{*_join()} will automatically run \code{ANALYZE} on the created table in the hope that this will make you queries as efficient as possible by giving more data to the query planner. This allows you to join tables across srcs, but it's potentially expensive operation so you must opt into it.} \item{...}{Not currently used; provided for future extensions.} \item{all}{If \code{TRUE}, includes all matches in output, not just unique rows.} } \description{ These are methods for the dplyr generics \code{dplyr::intersect()}, \code{dplyr::union()}, and \code{dplyr::setdiff()}. They are translated to \code{INTERSECT}, \code{UNION}, and \code{EXCEPT} respectively. } dbplyr/man/backend-postgres.Rd0000644000176200001440000000153514002647450016051 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-postgres.R \name{backend-postgres} \alias{simulate_postgres} \title{Backend: PostgreSQL} \usage{ simulate_postgres() } \description{ See \code{vignette("translate-function")} and \code{vignette("translate-verb")} for details of overall translation technology. Key differences for this backend are: \itemize{ \item Many stringr functions \item lubridate date-time extraction functions \item More standard statistical summaries } Use \code{simulate_postgres()} with \code{lazy_frame()} to see simulated SQL without converting to live access database. } \examples{ library(dplyr, warn.conflicts = FALSE) lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_postgres()) lf \%>\% summarise(x = sd(b, na.rm = TRUE)) lf \%>\% summarise(y = cor(b, c), y = cov(b, c)) } dbplyr/man/reexports.Rd0000644000176200001440000000062214002647450014645 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/reexport.R \docType{import} \name{reexports} \alias{reexports} \alias{\%>\%} \title{Objects exported from other packages} \keyword{internal} \description{ These objects are imported from other packages. Follow the links below to see their documentation. \describe{ \item{magrittr}{\code{\link[magrittr:pipe]{\%>\%}}} }} dbplyr/man/ident.Rd0000644000176200001440000000135714002647450013723 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/ident.R \name{ident} \alias{ident} \alias{is.ident} \title{Flag a character vector as SQL identifiers} \usage{ ident(...) is.ident(x) } \arguments{ \item{...}{A character vector, or name-value pairs} \item{x}{An object} } \description{ \code{ident()} takes unquoted strings and flags them as identifiers. \code{ident_q()} assumes its input has already been quoted, and ensures it does not get quoted again. This is currently used only for for \code{schema.table}. } \examples{ # SQL92 quotes strings with ' escape_ansi("x") # And identifiers with " ident("x") escape_ansi(ident("x")) # You can supply multiple inputs ident(a = "x", b = "y") ident_q(a = "x", b = "y") } dbplyr/man/lazy_ops.Rd0000644000176200001440000000177213417126252014462 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/lazy-ops.R \name{lazy_ops} \alias{lazy_ops} \alias{op_base} \alias{op_single} \alias{add_op_single} \alias{op_double} \alias{op_grps} \alias{op_vars} \alias{op_sort} \alias{op_frame} \title{Lazy operations} \usage{ op_base(x, vars, class = character()) op_single(name, x, dots = list(), args = list()) add_op_single(name, .data, dots = list(), args = list()) op_double(name, x, y, args = list()) op_grps(op) op_vars(op) op_sort(op) op_frame(op) } \description{ This set of S3 classes describe the action of dplyr verbs. These are currently used for SQL sources to separate the description of operations in R from their computation in SQL. This API is very new so is likely to evolve in the future. } \details{ \code{op_vars()} and \code{op_grps()} compute the variables and groups from a sequence of lazy operations. \code{op_sort()} and \code{op_frame()} tracks the order and frame for use in window functions. } \keyword{internal} dbplyr/man/simulate_dbi.Rd0000644000176200001440000000154714002647450015262 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-hive.R, R/backend-impala.R, % R/backend-mssql.R, R/simulate.R \name{simulate_hive} \alias{simulate_hive} \alias{simulate_impala} \alias{simulate_mssql} \alias{simulate_dbi} \title{Simulate database connections} \usage{ simulate_hive() simulate_impala() simulate_mssql(version = "15.0") simulate_dbi(class = character(), ...) } \description{ These functions generate S3 objects that have been designed to simulate the action of a database connection, without actually having the database available. Obviously, this simulation can only be incomplete, but most importantly it allows us to simulate SQL generation for any database without actually connecting to it. } \details{ Simulated SQL always quotes identifies with \code{`x`}, and strings with \code{'x'}. } \keyword{internal} dbplyr/man/window_order.Rd0000644000176200001440000000157114002647450015320 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-window.R \name{window_order} \alias{window_order} \alias{window_frame} \title{Override window order and frame} \usage{ window_order(.data, ...) window_frame(.data, from = -Inf, to = Inf) } \arguments{ \item{.data}{A lazy data frame backed by a database query.} \item{...}{Variables to order by} \item{from, to}{Bounds of the frame.} } \description{ These allow you to override the \verb{PARTITION BY} and \verb{ORDER BY} clauses of window functions generated by grouped mutates. } \examples{ library(dplyr, warn.conflicts = FALSE) db <- memdb_frame(g = rep(1:2, each = 5), y = runif(10), z = 1:10) db \%>\% window_order(y) \%>\% mutate(z = cumsum(y)) \%>\% show_query() db \%>\% group_by(g) \%>\% window_frame(-3, 0) \%>\% window_order(z) \%>\% mutate(z = sum(x)) \%>\% show_query() } dbplyr/man/copy_to.src_sql.Rd0000644000176200001440000000551114002647450015735 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-copy-to.R \name{copy_to.src_sql} \alias{copy_to.src_sql} \title{Copy a local data frame to a remote database} \usage{ \method{copy_to}{src_sql}( dest, df, name = deparse(substitute(df)), overwrite = FALSE, types = NULL, temporary = TRUE, unique_indexes = NULL, indexes = NULL, analyze = TRUE, ..., in_transaction = TRUE ) } \arguments{ \item{dest}{remote data source} \item{df}{A local data frame, a \code{tbl_sql} from same source, or a \code{tbl_sql} from another source. If from another source, all data must transition through R in one pass, so it is only suitable for transferring small amounts of data.} \item{name}{name for new remote table.} \item{overwrite}{If \code{TRUE}, will overwrite an existing table with name \code{name}. If \code{FALSE}, will throw an error if \code{name} already exists.} \item{types}{a character vector giving variable types to use for the columns. See \url{https://www.sqlite.org/datatype3.html} for available types.} \item{temporary}{if \code{TRUE}, will create a temporary table that is local to this connection and will be automatically deleted when the connection expires} \item{unique_indexes}{a list of character vectors. Each element of the list will create a new unique index over the specified column(s). Duplicate rows will result in failure.} \item{indexes}{a list of character vectors. Each element of the list will create a new index.} \item{analyze}{if \code{TRUE} (the default), will automatically ANALYZE the new table so that the query optimiser has useful information.} \item{...}{other parameters passed to methods.} \item{in_transaction}{Should the table creation be wrapped in a transaction? This typically makes things faster, but you may want to suppress if the database doesn't support transactions, or you're wrapping in a transaction higher up (and your database doesn't support nested transactions.)} } \value{ Another \code{tbl_lazy}. Use \code{\link[=show_query]{show_query()}} to see the generated query, and use \code{\link[=collect.tbl_sql]{collect()}} to execute the query and return data to R. } \description{ This is an implementation of the dplyr \code{\link[=copy_to]{copy_to()}} generic and it mostly a wrapper around \code{\link[DBI:dbWriteTable]{DBI::dbWriteTable()}}. It is useful for copying small amounts of data to a database for examples, experiments, and joins. By default, it creates temporary tables which are only visible within the current connection to the database. } \examples{ library(dplyr, warn.conflicts = FALSE) df <- data.frame(x = 1:5, y = letters[5:1]) db <- copy_to(src_memdb(), df) db df2 <- data.frame(y = c("a", "d"), fruit = c("apple", "date")) # copy_to() is called automatically if you set copy = TRUE # in the join functions db \%>\% left_join(df2, copy = TRUE) } dbplyr/man/backend-oracle.Rd0000644000176200001440000000163114002647450015445 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-oracle.R \name{backend-oracle} \alias{simulate_oracle} \title{Backend: Oracle} \usage{ simulate_oracle() } \description{ See \code{vignette("translate-function")} and \code{vignette("translate-verb")} for details of overall translation technology. Key differences for this backend are: \itemize{ \item Use \verb{FETCH FIRST} instead of \code{LIMIT} \item Custom types \item \code{paste()} uses \code{||} \item Custom subquery generation (no \code{AS}) \item \code{setdiff()} uses \code{MINUS} instead of \code{EXCEPT} } Use \code{simulate_oracle()} with \code{lazy_frame()} to see simulated SQL without converting to live access database. } \examples{ library(dplyr, warn.conflicts = FALSE) lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_oracle()) lf \%>\% transmute(x = paste0(c, " times")) lf \%>\% setdiff(lf) } dbplyr/man/in_schema.Rd0000644000176200001440000000161014002647450014536 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/schema.R \name{in_schema} \alias{in_schema} \title{Refer to a table in a schema} \usage{ in_schema(schema, table) } \arguments{ \item{schema, table}{Names of schema and table. These will be automatically quoted; use \code{sql()} to pass a raw name that won't get quoted.} } \description{ Refer to a table in a schema } \examples{ in_schema("my_schema", "my_table") # eliminate quotes in_schema(sql("my_schema"), sql("my_table")) # Example using schemas with SQLite con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") # Add auxilary schema tmp <- tempfile() DBI::dbExecute(con, paste0("ATTACH '", tmp, "' AS aux")) library(dplyr, warn.conflicts = FALSE) copy_to(con, iris, "df", temporary = FALSE) copy_to(con, mtcars, in_schema("aux", "df"), temporary = FALSE) con \%>\% tbl("df") con \%>\% tbl(in_schema("aux", "df")) } dbplyr/man/escape.Rd0000644000176200001440000000270313416377202014057 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/escape.R \name{escape} \alias{escape} \alias{escape_ansi} \alias{sql_vector} \title{Escape/quote a string.} \usage{ escape(x, parens = NA, collapse = " ", con = NULL) escape_ansi(x, parens = NA, collapse = "") sql_vector(x, parens = NA, collapse = " ", con = NULL) } \arguments{ \item{x}{An object to escape. Existing sql vectors will be left as is, character vectors are escaped with single quotes, numeric vectors have trailing \code{.0} added if they're whole numbers, identifiers are escaped with double quotes.} \item{parens, collapse}{Controls behaviour when multiple values are supplied. \code{parens} should be a logical flag, or if \code{NA}, will wrap in parens if length > 1. Default behaviour: lists are always wrapped in parens and separated by commas, identifiers are separated by commas and never wrapped, atomic vectors are separated by spaces and wrapped in parens if needed.} \item{con}{Database connection.} } \description{ \code{escape()} requires you to provide a database connection to control the details of escaping. \code{escape_ansi()} uses the SQL 92 ANSI standard. } \examples{ # Doubles vs. integers escape_ansi(1:5) escape_ansi(c(1, 5.4)) # String vs known sql vs. sql identifier escape_ansi("X") escape_ansi(sql("X")) escape_ansi(ident("X")) # Escaping is idempotent escape_ansi("X") escape_ansi(escape_ansi("X")) escape_ansi(escape_ansi(escape_ansi("X"))) } dbplyr/man/build_sql.Rd0000644000176200001440000000310313474056125014572 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/build-sql.R \name{build_sql} \alias{build_sql} \title{Build a SQL string.} \usage{ build_sql(..., .env = parent.frame(), con = sql_current_con()) } \arguments{ \item{...}{input to convert to SQL. Use \code{\link[=sql]{sql()}} to preserve user input as is (dangerous), and \code{\link[=ident]{ident()}} to label user input as sql identifiers (safe)} \item{.env}{the environment in which to evaluate the arguments. Should not be needed in typical use.} \item{con}{database connection; used to select correct quoting characters.} } \description{ This is a convenience function that should prevent sql injection attacks (which in the context of dplyr are most likely to be accidental not deliberate) by automatically escaping all expressions in the input, while treating bare strings as sql. This is unlikely to prevent any serious attack, but should make it unlikely that you produce invalid sql. } \details{ This function should be used only when generating \code{SELECT} clauses, other high level queries, or for other syntax that has no R equivalent. For individual function translations, prefer \code{\link[=sql_expr]{sql_expr()}}. } \examples{ con <- simulate_dbi() build_sql("SELECT * FROM TABLE", con = con) x <- "TABLE" build_sql("SELECT * FROM ", x, con = con) build_sql("SELECT * FROM ", ident(x), con = con) build_sql("SELECT * FROM ", sql(x), con = con) # http://xkcd.com/327/ name <- "Robert'); DROP TABLE Students;--" build_sql("INSERT INTO Students (Name) VALUES (", name, ")", con = con) } \keyword{internal} dbplyr/man/src_sql.Rd0000644000176200001440000000104114002647450014254 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/src-sql.R \name{src_sql} \alias{src_sql} \title{Create a "sql src" object} \usage{ src_sql(subclass, con, ...) } \arguments{ \item{subclass}{name of subclass. "src_sql" is an abstract base class, so you must supply this value. \code{src_} is automatically prepended to the class name} \item{con}{the connection object} \item{...}{fields used by object} } \description{ Deprecated: please use directly use a \code{DBIConnection} object instead. } \keyword{internal} dbplyr/man/backend-snowflake.Rd0000644000176200001440000000121114031447261016163 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-snowflake.R \name{backend-snowflake} \alias{simulate_snowflake} \title{Backend: Snowflake} \usage{ simulate_snowflake() } \description{ See \code{vignette("translate-function")} and \code{vignette("translate-verb")} for details of overall translation technology. Use \code{simulate_snowflake()} with \code{lazy_frame()} to see simulated SQL without converting to live access database. } \examples{ library(dplyr, warn.conflicts = FALSE) lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_snowflake()) lf \%>\% transmute(x = paste0(z, " times")) } dbplyr/man/backend-sqlite.Rd0000644000176200001440000000162614004012136015472 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-sqlite.R \name{backend-sqlite} \alias{simulate_sqlite} \title{Backend: SQLite} \usage{ simulate_sqlite() } \description{ See \code{vignette("translate-function")} and \code{vignette("translate-verb")} for details of overall translation technology. Key differences for this backend are: \itemize{ \item Uses non-standard \code{LOG()} function \item Date-time extraction functions from lubridate \item Custom median translation \item Right and full joins are simulated using left joins } Use \code{simulate_sqlite()} with \code{lazy_frame()} to see simulated SQL without converting to live access database. } \examples{ library(dplyr, warn.conflicts = FALSE) lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_sqlite()) lf \%>\% transmute(x = paste(c, " times")) lf \%>\% transmute(x = log(b), y = log(b, base = 2)) } dbplyr/man/pull.tbl_sql.Rd0000644000176200001440000000205114002647450015223 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-pull.R \name{pull.tbl_sql} \alias{pull.tbl_sql} \title{Extract a single column} \usage{ \method{pull}{tbl_sql}(.data, var = -1) } \arguments{ \item{.data}{A lazy data frame backed by a database query.} \item{var}{A variable specified as: \itemize{ \item a literal variable name \item a positive integer, giving the position counting from the left \item a negative integer, giving the position counting from the right. } The default returns the last column (on the assumption that's the column you've created most recently). This argument is taken by expression and supports \link[rlang:nse-force]{quasiquotation} (you can unquote column names and column locations).} } \value{ A vector of data. } \description{ This is a method for the dplyr \code{\link[=pull]{pull()}} generic. It evaluates the query retrieving just the specified column. } \examples{ library(dplyr, warn.conflicts = FALSE) db <- memdb_frame(x = 1:5, y = 5:1) db \%>\% mutate(z = x + y * 2) \%>\% pull() } dbplyr/man/backend-teradata.Rd0000644000176200001440000000136414002647450015770 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-teradata.R \name{backend-teradata} \alias{simulate_teradata} \title{Backend: Teradata} \usage{ simulate_teradata() } \description{ See \code{vignette("translate-function")} and \code{vignette("translate-verb")} for details of overall translation technology. Key differences for this backend are: \itemize{ \item Uses \code{TOP} instead of \code{LIMIT} \item Selection of user supplied translations } Use \code{simulate_teradata()} with \code{lazy_frame()} to see simulated SQL without converting to live access database. } \examples{ library(dplyr, warn.conflicts = FALSE) lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_teradata()) lf \%>\% head() } dbplyr/man/complete.tbl_lazy.Rd0000644000176200001440000000243014004012136016225 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-expand.R \name{complete.tbl_lazy} \alias{complete.tbl_lazy} \title{Complete a SQL table with missing combinations of data} \usage{ complete.tbl_lazy(data, ..., fill = list()) } \arguments{ \item{data}{A lazy data frame backed by a database query.} \item{...}{Specification of columns to expand. See \link[tidyr:expand]{tidyr::expand} for more details.} \item{fill}{A named list that for each variable supplies a single value to use instead of NA for missing combinations.} } \value{ Another \code{tbl_lazy}. Use \code{\link[=show_query]{show_query()}} to see the generated query, and use \code{\link[=collect.tbl_sql]{collect()}} to execute the query and return data to R. } \description{ Turns implicit missing values into explicit missing values. This is a method for the \code{\link[tidyr:complete]{tidyr::complete()}} generic. } \examples{ if (require("tidyr", quietly = TRUE)) { df <- memdb_frame( group = c(1:2, 1), item_id = c(1:2, 2), item_name = c("a", "b", "b"), value1 = 1:3, value2 = 4:6 ) df \%>\% complete(group, nesting(item_id, item_name)) # You can also choose to fill in missing values df \%>\% complete(group, nesting(item_id, item_name), fill = list(value1 = 0)) } } dbplyr/man/memdb_frame.Rd0000644000176200001440000000274414002647450015057 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/memdb.R \name{memdb_frame} \alias{memdb_frame} \alias{tbl_memdb} \alias{src_memdb} \title{Create a database table in temporary in-memory database.} \usage{ memdb_frame(..., .name = unique_table_name()) tbl_memdb(df, name = deparse(substitute(df))) src_memdb() } \arguments{ \item{...}{<\code{\link[rlang:dyn-dots]{dynamic-dots}}> A set of name-value pairs. These arguments are processed with \code{\link[rlang:nse-defuse]{rlang::quos()}} and support unquote via \code{\link{!!}} and unquote-splice via \code{\link{!!!}}. Use \verb{:=} to create columns that start with a dot. Arguments are evaluated sequentially. You can refer to previously created elements directly or using the \link{.data} pronoun. An existing \code{.data} pronoun, provided e.g. inside \code{\link[dplyr:mutate]{dplyr::mutate()}}, is not available.} \item{df}{Data frame to copy} \item{name, .name}{Name of table in database: defaults to a random name that's unlikely to conflict with an existing table.} } \description{ \code{memdb_frame()} works like \code{\link[tibble:tibble]{tibble::tibble()}}, but instead of creating a new data frame in R, it creates a table in \code{\link[=src_memdb]{src_memdb()}}. } \examples{ library(dplyr) df <- memdb_frame(x = runif(100), y = runif(100)) df \%>\% arrange(x) df \%>\% arrange(x) \%>\% show_query() mtcars_db <- tbl_memdb(mtcars) mtcars_db \%>\% group_by(cyl) \%>\% summarise(n = n()) \%>\% show_query() } dbplyr/man/dbplyr-slice.Rd0000644000176200001440000000461614002647450015212 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-slice.R \name{dbplyr-slice} \alias{slice_min.tbl_lazy} \alias{slice_max.tbl_lazy} \alias{slice_sample.tbl_lazy} \title{Subset rows using their positions} \usage{ \method{slice_min}{tbl_lazy}(.data, order_by, ..., n, prop, with_ties = TRUE) \method{slice_max}{tbl_lazy}(.data, order_by, ..., n, prop, with_ties = TRUE) \method{slice_sample}{tbl_lazy}(.data, ..., n, prop, weight_by = NULL, replace = FALSE) } \arguments{ \item{.data}{A lazy data frame backed by a database query.} \item{order_by}{Variable or function of variables to order by.} \item{...}{Not used.} \item{n, prop}{Provide either \code{n}, the number of rows, or \code{prop}, the proportion of rows to select. If neither are supplied, \code{n = 1} will be used. If \code{n} is greater than the number of rows in the group (or \code{prop} > 1), the result will be silently truncated to the group size. If the proportion of a group size is not an integer, it is rounded down.} \item{with_ties}{Should ties be kept together? The default, \code{TRUE}, may return more rows than you request. Use FALSE to ignore ties, and return the first n rows.} \item{weight_by, replace}{Not supported for database backends.} } \description{ These are methods for the dplyr generics \code{\link[=slice_min]{slice_min()}}, \code{\link[=slice_max]{slice_max()}}, and \code{\link[=slice_sample]{slice_sample()}}. They are translated to SQL using \code{\link[=filter]{filter()}} and window functions (\code{ROWNUMBER}, \code{MIN_RANK}, or \code{CUME_DIST} depending on arguments). \code{slice()}, \code{slice_head()}, and \code{slice_tail()} are not supported since database tables have no intrinsic order. If data is grouped, the operation will be performed on each group so that (e.g.) \code{slice_min(db, x, n = 3)} will select the three rows with the smallest value of \code{x} in each group. } \examples{ library(dplyr, warn.conflicts = FALSE) db <- memdb_frame(x = 1:3, y = c(1, 1, 2)) db \%>\% slice_min(x) \%>\% show_query() db \%>\% slice_max(x) \%>\% show_query() db \%>\% slice_sample() \%>\% show_query() db \%>\% group_by(y) \%>\% slice_min(x) \%>\% show_query() # By default, ties are includes so you may get more rows # than you expect db \%>\% slice_min(y, n = 1) db \%>\% slice_min(y, n = 1, with_ties = FALSE) # Non-integer group sizes are rounded down db \%>\% slice_min(x, prop = 0.5) } dbplyr/man/named_commas.Rd0000644000176200001440000000057013474056125015244 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/utils.R \name{named_commas} \alias{named_commas} \title{Provides comma-separated string out of the parameters} \usage{ named_commas(...) } \arguments{ \item{...}{Arguments to be constructed into the string} } \description{ Provides comma-separated string out of the parameters } \keyword{internal} dbplyr/man/group_by.tbl_lazy.Rd0000644000176200001440000000302014015732330016246 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verb-group_by.R \name{group_by.tbl_lazy} \alias{group_by.tbl_lazy} \title{Group by one or more variables} \usage{ \method{group_by}{tbl_lazy}(.data, ..., .add = FALSE, add = NULL, .drop = TRUE) } \arguments{ \item{.data}{A lazy data frame backed by a database query.} \item{...}{<\code{\link[dplyr:dplyr_data_masking]{data-masking}}> Variables, or functions of variables. Use \code{\link[dplyr:desc]{desc()}} to sort a variable in descending order.} \item{.add}{When \code{FALSE}, the default, \code{group_by()} will override existing groups. To add to the existing groups, use \code{.add = TRUE}. This argument was previously called \code{add}, but that prevented creating a new grouping variable called \code{add}, and conflicts with our naming conventions.} \item{add}{Deprecated. Please use \code{.add} instead.} \item{.drop}{Not supported by this method.} } \description{ This is a method for the dplyr \code{\link[=group_by]{group_by()}} generic. It is translated to the \verb{GROUP BY} clause of the SQL query when used with \code{\link[=summarise.tbl_lazy]{summarise()}} and to the \verb{PARTITION BY} clause of window functions when used with \code{\link[=mutate.tbl_lazy]{mutate()}}. } \examples{ library(dplyr, warn.conflicts = FALSE) db <- memdb_frame(g = c(1, 1, 1, 2, 2), x = c(4, 3, 6, 9, 2)) db \%>\% group_by(g) \%>\% summarise(n()) \%>\% show_query() db \%>\% group_by(g) \%>\% mutate(x2 = x / sum(x, na.rm = TRUE)) \%>\% show_query() } dbplyr/man/backend-mysql.Rd0000644000176200001440000000155614002647450015353 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-mysql.R \name{backend-mysql} \alias{simulate_mysql} \title{Backend: MySQL/MariaDB} \usage{ simulate_mysql() } \description{ See \code{vignette("translate-function")} and \code{vignette("translate-verb")} for details of overall translation technology. Key differences for this backend are: \itemize{ \item \code{paste()} uses \code{CONCAT_WS()} \item String translations for \code{str_detect()}, \code{str_locate()}, and \code{str_replace_all()} \item Clear error message for unsupported full joins } Use \code{simulate_mysql()} with \code{lazy_frame()} to see simulated SQL without converting to live access database. } \examples{ library(dplyr, warn.conflicts = FALSE) lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_mysql()) lf \%>\% transmute(x = paste0(z, " times")) } dbplyr/man/backend-redshift.Rd0000644000176200001440000000135714002647450016015 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-redshift.R \name{backend-redshift} \alias{simulate_redshift} \title{Backend: Redshift} \usage{ simulate_redshift() } \description{ Base translations come from \link[=simulate_postgres]{PostgreSQL backend}. There are generally few differences, apart from string manipulation. Use \code{simulate_redshift()} with \code{lazy_frame()} to see simulated SQL without converting to live access database. } \examples{ library(dplyr, warn.conflicts = FALSE) lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_redshift()) lf \%>\% transmute(x = paste(c, " times")) lf \%>\% transmute(x = substr(c, 2, 3)) lf \%>\% transmute(x = str_replace_all(c, "a", "z")) } dbplyr/man/db-quote.Rd0000644000176200001440000000201614002647450014331 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/db-escape.R \name{db-quote} \alias{sql_escape_logical} \alias{sql_escape_date} \alias{sql_escape_datetime} \alias{sql_escape_raw} \title{SQL escaping/quoting generics} \usage{ sql_escape_logical(con, x) sql_escape_date(con, x) sql_escape_datetime(con, x) sql_escape_raw(con, x) } \description{ These generics translate individual values into SQL. The core generics are \code{\link[DBI:dbQuoteIdentifier]{DBI::dbQuoteIdentifier()}} and\link[DBI:dbQuoteString]{DBI::dbQuoteString} for quoting identifiers and strings, but dbplyr needs additional tools for inserting logical, date, date-time, and raw values into queries. } \examples{ con <- simulate_dbi() sql_escape_logical(con, c(TRUE, FALSE, NA)) sql_escape_date(con, Sys.Date()) sql_escape_date(con, Sys.time()) sql_escape_raw(con, charToRaw("hi")) } \seealso{ Other generic: \code{\link{db-sql}}, \code{\link{db_connection_describe}()}, \code{\link{db_copy_to}()} } \concept{generic} \keyword{internal} dbplyr/man/backend-access.Rd0000644000176200001440000000172114002647450015441 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-access.R \name{backend-access} \alias{simulate_access} \title{Backend: MS Access} \usage{ simulate_access() } \description{ See \code{vignette("translate-function")} and \code{vignette("translate-verb")} for details of overall translation technology. Key differences for this backend are: \itemize{ \item \code{SELECT} uses \code{TOP}, not \code{LIMIT} \item Non-standard types and mathematical functions \item String concatenation uses \code{&} \item No \code{ANALYZE} equivalent \item \code{TRUE} and \code{FALSE} converted to 1 and 0 } Use \code{simulate_access()} with \code{lazy_frame()} to see simulated SQL without converting to live access database. } \examples{ library(dplyr, warn.conflicts = FALSE) lf <- lazy_frame(x = 1, y = 2, z = "a", con = simulate_access()) lf \%>\% head() lf \%>\% mutate(y = as.numeric(y), z = sqrt(x^2 + 10)) lf \%>\% mutate(a = paste0(z, " times")) } dbplyr/man/tbl.src_dbi.Rd0000644000176200001440000000607714004012136014776 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/src_dbi.R \name{tbl.src_dbi} \alias{tbl.src_dbi} \alias{tbl_dbi} \title{Use dplyr verbs with a remote database table} \usage{ \method{tbl}{src_dbi}(src, from, ...) } \arguments{ \item{src}{A \code{DBIConnection} object produced by \code{DBI::dbConnect()}.} \item{from}{Either a string (giving a table name), a fully qualified table name created by \code{\link[=in_schema]{in_schema()}} or a literal \code{\link[=sql]{sql()}} string.} \item{...}{Passed on to \code{\link[=tbl_sql]{tbl_sql()}}} } \description{ All data manipulation on SQL tbls are lazy: they will not actually run the query or retrieve the data unless you ask for it: they all return a new \code{tbl_dbi} object. Use \code{\link[=compute]{compute()}} to run the query and save the results in a temporary in the database, or use \code{\link[=collect]{collect()}} to retrieve the results to R. You can see the query with \code{\link[=show_query]{show_query()}}. } \details{ For best performance, the database should have an index on the variables that you are grouping by. Use \code{\link[=explain]{explain()}} to check that the database is using the indexes that you expect. There is one verb that is not lazy: \code{\link[=do]{do()}} is eager because it must pull the data into R. } \examples{ library(dplyr) # Connect to a temporary in-memory SQLite database con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") # Add some data copy_to(con, mtcars) DBI::dbListTables(con) # To retrieve a single table from a source, use `tbl()` con \%>\% tbl("mtcars") # Use `in_schema()` for fully qualified table names con \%>\% tbl(in_schema("temp", "mtcars")) \%>\% head(1) # You can also use pass raw SQL if you want a more sophisticated query con \%>\% tbl(sql("SELECT * FROM mtcars WHERE cyl = 8")) # If you just want a temporary in-memory database, use src_memdb() src2 <- src_memdb() # To show off the full features of dplyr's database integration, # we'll use the Lahman database. lahman_sqlite() takes care of # creating the database. if (requireNamespace("Lahman", quietly = TRUE)) { batting <- copy_to(con, Lahman::Batting) batting # Basic data manipulation verbs work in the same way as with a tibble batting \%>\% filter(yearID > 2005, G > 130) batting \%>\% select(playerID:lgID) batting \%>\% arrange(playerID, desc(yearID)) batting \%>\% summarise(G = mean(G), n = n()) # There are a few exceptions. For example, databases give integer results # when dividing one integer by another. Multiply by 1 to fix the problem batting \%>\% select(playerID:lgID, AB, R, G) \%>\% mutate( R_per_game1 = R / G, R_per_game2 = R * 1.0 / G ) # All operations are lazy: they don't do anything until you request the # data, either by `print()`ing it (which shows the first ten rows), # or by `collect()`ing the results locally. system.time(recent <- filter(batting, yearID > 2010)) system.time(collect(recent)) # You can see the query that dplyr creates with show_query() batting \%>\% filter(G > 0) \%>\% group_by(playerID) \%>\% summarise(n = n()) \%>\% show_query() } } dbplyr/man/translate_sql.Rd0000644000176200001440000000734314006531012015463 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/translate-sql.R \name{translate_sql} \alias{translate_sql} \alias{translate_sql_} \title{Translate an expression to sql} \usage{ translate_sql( ..., con = NULL, vars = character(), vars_group = NULL, vars_order = NULL, vars_frame = NULL, window = TRUE ) translate_sql_( dots, con = NULL, vars_group = NULL, vars_order = NULL, vars_frame = NULL, window = TRUE, context = list() ) } \arguments{ \item{..., dots}{Expressions to translate. \code{translate_sql()} automatically quotes them for you. \code{translate_sql_()} expects a list of already quoted objects.} \item{con}{An optional database connection to control the details of the translation. The default, \code{NULL}, generates ANSI SQL.} \item{vars}{Deprecated. Now call \code{\link[=partial_eval]{partial_eval()}} directly.} \item{vars_group, vars_order, vars_frame}{Parameters used in the \code{OVER} expression of windowed functions.} \item{window}{Use \code{FALSE} to suppress generation of the \code{OVER} statement used for window functions. This is necessary when generating SQL for a grouped summary.} \item{context}{Use to carry information for special translation cases. For example, MS SQL needs a different conversion for is.na() in WHERE vs. SELECT clauses. Expects a list.} } \description{ Translate an expression to sql } \section{Base translation}{ The base translator, \code{base_sql}, provides custom mappings for for commonly used base functions including logical (\code{!}, \code{&}, \code{|}), arithmetic (\code{^}), and comparison (\code{!=}) operators, as well as common summary (\code{mean()}, \code{var()}) and manipulation functions. All other functions will be preserved as is. R's infix functions (e.g. \verb{\%like\%}) will be converted to their SQL equivalents (e.g. \code{LIKE}). You can use this to access SQL string concatenation: \code{||} is mapped to \code{OR}, but \verb{\%||\%} is mapped to \code{||}. To suppress this behaviour, and force errors immediately when dplyr doesn't know how to translate a function it encounters, using set the \code{dplyr.strict_sql} option to \code{TRUE}. You can also use \code{\link[=sql]{sql()}} to insert a raw sql string. } \section{SQLite translation}{ The SQLite variant currently only adds one additional function: a mapping from \code{sd()} to the SQL aggregation function \code{STDEV}. } \examples{ # Regular maths is translated in a very straightforward way translate_sql(x + 1) translate_sql(sin(x) + tan(y)) # Note that all variable names are escaped translate_sql(like == "x") # In ANSI SQL: "" quotes variable _names_, '' quotes strings # Logical operators are converted to their sql equivalents translate_sql(x < 5 & !(y >= 5)) # xor() doesn't have a direct SQL equivalent translate_sql(xor(x, y)) # If is translated into case when translate_sql(if (x > 5) "big" else "small") # Infix functions are passed onto SQL with \% removed translate_sql(first \%like\% "Had\%") translate_sql(first \%is\% NA) translate_sql(first \%in\% c("John", "Roger", "Robert")) # And be careful if you really want integers translate_sql(x == 1) translate_sql(x == 1L) # If you have an already quoted object, use translate_sql_: x <- quote(y + 1 / sin(t)) translate_sql_(list(x), con = simulate_dbi()) # Windowed translation -------------------------------------------- # Known window functions automatically get OVER() translate_sql(mpg > mean(mpg)) # Suppress this with window = FALSE translate_sql(mpg > mean(mpg), window = FALSE) # vars_group controls partition: translate_sql(mpg > mean(mpg), vars_group = "cyl") # and vars_order controls ordering for those functions that need it translate_sql(cumsum(mpg)) translate_sql(cumsum(mpg), vars_order = "mpg") } dbplyr/DESCRIPTION0000644000176200001440000000644414033054373013266 0ustar liggesusersType: Package Package: dbplyr Title: A 'dplyr' Back End for Databases Version: 2.1.1 Authors@R: c(person(given = "Hadley", family = "Wickham", role = c("aut", "cre"), email = "hadley@rstudio.com"), person(given = "Maximilian", family = "Girlich", role = "aut"), person(given = "Edgar", family = "Ruiz", role = "aut"), person(given = "RStudio", role = c("cph", "fnd"))) Description: A 'dplyr' back end for databases that allows you to work with remote database tables as if they are in-memory data frames. Basic features works with any database that has a 'DBI' back end; more advanced features require 'SQL' translation to be provided by the package author. License: MIT + file LICENSE URL: https://dbplyr.tidyverse.org/, https://github.com/tidyverse/dbplyr BugReports: https://github.com/tidyverse/dbplyr/issues Depends: R (>= 3.1) Imports: assertthat (>= 0.2.0), blob (>= 1.2.0), DBI (>= 1.0.0), dplyr (>= 1.0.4), ellipsis, glue (>= 1.2.0), lifecycle (>= 1.0.0), magrittr, methods, purrr (>= 0.2.5), R6 (>= 2.2.2), rlang (>= 0.2.0), tibble (>= 1.4.2), tidyselect (>= 0.2.4), utils, vctrs, withr Suggests: bit64, covr, knitr, Lahman, nycflights13, odbc, RMariaDB (>= 1.0.2), rmarkdown, RPostgres (>= 1.1.3), RPostgreSQL, RSQLite (>= 2.1.0), testthat (>= 3.0.2), tidyr VignetteBuilder: knitr Config/testthat/edition: 3 Encoding: UTF-8 Language: en-gb RoxygenNote: 7.1.1 Collate: 'utils.R' 'sql.R' 'escape.R' 'translate-sql-quantile.R' 'translate-sql-string.R' 'translate-sql-paste.R' 'translate-sql-helpers.R' 'translate-sql-window.R' 'translate-sql-conditional.R' 'backend-.R' 'backend-access.R' 'backend-hana.R' 'backend-hive.R' 'backend-impala.R' 'backend-mssql.R' 'backend-mysql.R' 'backend-odbc.R' 'backend-oracle.R' 'backend-postgres.R' 'backend-postgres-old.R' 'backend-redshift.R' 'backend-snowflake.R' 'backend-sqlite.R' 'backend-teradata.R' 'build-sql.R' 'data-cache.R' 'data-lahman.R' 'data-nycflights13.R' 'db-escape.R' 'db-io.R' 'db-sql.R' 'db.R' 'dbplyr.R' 'explain.R' 'ident.R' 'lazy-ops.R' 'memdb.R' 'partial-eval.R' 'progress.R' 'query-join.R' 'query-select.R' 'query-semi-join.R' 'query-set-op.R' 'query.R' 'reexport.R' 'remote.R' 'schema.R' 'simulate.R' 'sql-build.R' 'sql-clause.R' 'sql-expr.R' 'src-sql.R' 'src_dbi.R' 'tbl-lazy.R' 'tbl-sql.R' 'test-frame.R' 'testthat.R' 'translate-sql.R' 'utils-format.R' 'verb-arrange.R' 'verb-compute.R' 'verb-copy-to.R' 'verb-count.R' 'verb-distinct.R' 'verb-do-query.R' 'verb-do.R' 'verb-expand.R' 'verb-fill.R' 'verb-filter.R' 'verb-group_by.R' 'verb-head.R' 'verb-joins.R' 'verb-mutate.R' 'verb-pivot-longer.R' 'verb-pivot-wider.R' 'verb-pull.R' 'verb-select.R' 'verb-set-ops.R' 'verb-slice.R' 'verb-summarise.R' 'verb-uncount.R' 'verb-window.R' 'zzz.R' NeedsCompilation: no Packaged: 2021-04-06 11:29:51 UTC; hadley Author: Hadley Wickham [aut, cre], Maximilian Girlich [aut], Edgar Ruiz [aut], RStudio [cph, fnd] Maintainer: Hadley Wickham Repository: CRAN Date/Publication: 2021-04-06 12:50:02 UTC dbplyr/build/0000755000176200001440000000000014033043056012643 5ustar liggesusersdbplyr/build/vignette.rds0000644000176200001440000000061414033043056015203 0ustar liggesusersRMS0---?0QXRLۋ-Mm/Xuul&L% e+'t!Xeer,xT3QoRN^"#~\4Tsn bԀ\Jpj!ZC ant9o{7NwI[iBVo$}lW {G墶?<_ta*b]Aqpt/Dui,!8cu;L㷢iDHUFgC%dbplyr/build/dbplyr.pdf0000644000176200001440000054110214033043046014634 0ustar liggesusers%PDF-1.5 % 192 0 obj << /Length 936 /Filter /FlateDecode >> stream xڕVr6}WV{˒4i'sL(Xs1PI$㿯@6i=]̃/7n{= A`m=CC pHxky%_Y L4fWxRD >XE@ "}bQ~$2@ @ dZl<$CŇS2:O9 HV3i8BvplrC?L~3$Px.5f1S\MSH=3.()~Pԑi=WD ԯoHrzǴXsLgnPnKp2E ]6[J*ZK.xnU:Q,&<⹤$sZzS"q8ӵ0;jHgGvev[PcYΊcT% ӗ IKV5ӝ<*Y6 N:Χ 3pq"8hHx+ԕo[m,Ĺ#~k-D[4w$ج{>4zҼ:sD\8vZWdEzx1〃Rf˩!U}"ЀdZ$~y$srXN:Z}\.JA4kR(U%mLO) Ѧ+& >@uz> ۵BÊc0ԧqNиூ~/yD}AA4'\ڋc܉\ty.*%b-g7GSسL "7S,gZRӡ6[lZ|\DSe\q,t@V @F> stream xڭX]F}W!0THIZڤRt0fwi08q~}kݴ̽9YI,b92&)Hj$%iH:%JIYMJ"P4;,ƘtH92ld 4CV`5)VR,M`f)NDGQ)P-ل)9AAΊ V0YWbhJ ODs%]T v䦠$IO[KAj3.|d|VirF"mPE2YiI.}bw*vQP(htЏ bҥ@3ڡAQpAzKEI.(e(-%1pH%a M˳XZb S0džk )*t40!n~*%*e=hJSpSc=hr(DiPL1Rp!ių~ju z th]-?GX.eV˟vj8~ G[̻ʱ_m}9i"/q^΋D\ܺbbHylXlm㰿Gkk?9`b1ym-v &^4{錗 2Ƈy]̥h]qPdbѠ <YO-UolBtE55{}9w"kn|@yOoퟠ2ľuo׮6?PslCYϛmZ z3ԛ_B1DQtMeÁWWA~,(5+M7Gw|, .f&9~)eB qƠ0u7ZP xNMH>O~FoE=Q68cg>pQVe1zuuWy_6u.-.6roO.ziߪ΄ 8Eh$$C$~5㏭hq#?%#\'|C3Qk*2*nÉ?ɹP1&T5w3+QNlThq!~1 endstream endobj 203 0 obj << /Length 690 /Filter /FlateDecode >> stream xڕVr0+XLHלTqozT{. endstream endobj 211 0 obj << /Length 636 /Filter /FlateDecode >> stream xA E@=vLv;;Dلt ۸TI{^4N~<>/>>Qq!Hh9ѺnUuŁd0_RXՉ$g(U7~:X> GhN2,."RtL[$ G`?>S4SZm57'Mv:ډ3\ʆ" <ќ'i蔞捲|1Վ7lM/l 6 Fq/d=VO!YdGpYވin$xEΎi^f&]YUYkr[! W ]zv9kYx{~\/~93J endstream endobj 265 0 obj << /Length 958 /Filter /FlateDecode >> stream xIo8:J)W&1$jD7uPKQ9 PVby,B!CPOFcP "FϽ> }sKb 4"ۯCbxB~ 0M[d}bɎ? s^KX'bxsʾ1uOE50ď!?_- #놗1 Ax1v:q$EzUW~~35-P_7E3Ǖlmc^>t AN~S:kE  ev6kƤeS?JX8^Km39g[-%ؿ/ǹ4kvk'qvjNq JW-Vt[lS1U{]''YޣCG3z[icU_Wnnv/ (Ens^5S4I$4V7?uk>u>g6ݐ3Y\S[;$"JX⅘E8':߼Eqdgn908jP endstream endobj 307 0 obj << /Length 1609 /Filter /FlateDecode >> stream xڝX{o:"D%.rH&NVʧNthѴįQΣۓ'/HػyRĔD_(%;I-ůAu.1Ibn9ɏ$؆g{ɗoԛ{[ /d$ Jch%%qȼq%;^Gtp'Y:mNȇ#F}9jF~+_qR([ 3F(w >Q@BXH=QŲ)jF݁X9% uV(*;.F̗ͼY,̥%rO!|S<"4:C;}_a%Cw8˜YH=2|svi^v:ԳW/PoNEej=#Cx}QQ4.XMe֫ZM:L(0k4-(JNTRk'gQIEۀd;D4$q_@Ń8D&]P7%4| Ƨ0e}Ą0UN(˵˥Ee6GR?H`j^Lvm!ERnyݖS ;a,¹ánfYR3#zeQIMmqdPF;?0v䯿FR*ABoimE A`$J{MmUAFɛ,HaT4⹕QN~cӋ-Cc*-wBV~1IH\}>闫`渡fJ,\8bԾ܉͙\hy b Md6p_NY(x!  $NxOCӈ1Q5%>p&O܋p( p,$͒^( j@Xj7Jתr xa+޷ r gME>gvٞ^o˛= ʀ+kAB,KK9R(шitƯm3O˲J)LY*%,MƯr<֐iq[ͣZإ?ùT{DOQueT.( o?]suk09p;yZJK.`I(,)S=*/:;4%tyж?r6҅4byjCaM]0o\3zIFoD̑,w(_bEMLW=d>sxm/I6\\0qN4V|j8?CqPli9q#F+UL yn\/Lۆ==l1q]{YLtqsG(&}Td);!%a4Ĥb,>{L8EU \+*2h/9 3^ft1o *# 1ɔq ȑǢnpŃ#x ޳W,| h+uy8!e,V1rkAk Gʙ?ߘnaklF?'v6q endstream endobj 327 0 obj << /Length 1502 /Filter /FlateDecode >> stream xX[o6~2tE x]s6v+vɒ+I\PeӖX= ,:\)ؙ9脹NBxs}'1uM?tⲍGAKRDtҿP/tpmx!Y";oagϞ;$Mslb'qFk|8戂E垈"YNξ\gY9Ԧ(j-"F"GGfw*U,KH]Y0ͳCԟpSu-aQpʪSbQuFjي=VRF}drןT& :D7{ܦqzjx/?H/ uvaTþ)w80a|p{SF5kn+M[so%mJnYܻZՇpwv.vo endstream endobj 340 0 obj << /Length 1101 /Filter /FlateDecode >> stream xWFB2D{QBCڴGKj i8,}j}ϾR'FΌs`ɫgN"sƈqFEs"sF+[3pW/~b@  ;PLpox~67\"7УFWMC{]]Ԧ,E#J+jGV|Oۊf;>qtv`pv˵(Ŀ^(^?9 (gY)`:藕Jq9jՠ_Y.tn%419^I+I|$>> stream xڽZێ}a]X$/P  IId=YzG lsi퉖dsCT(C% JԹ&2sPv ټ,mˡZqj6I9@jXB @EƂ 3j ]#CA4^1cOڳ؅[CRޕxP YrP,H+ޫjP~f_+Aֆ!TCN\W9dY(V Q)!BkJÂV)䊵L!7t^Y1aDӴx/ f{aCx-,tWT,Z@3UP(XR؂IgF tUʾ73Vb9RQi5`kQm%_Ց:S\)c-5M{ (;!XTB}lvc0[ a)4*7*-ZN"`E Pk"-Zq`(4ZuS0 *V(%'V 5@s v5:S^9+&Pɓ:bi<x|P1;,u'sL۫?|×#9Ԇ\#GHv':~^+edp_ਵh2׻s?[>Re)׸p[Ԇ+/l8 "{$auzhb36?Ot endstream endobj 354 0 obj << /Length 2166 /Filter /FlateDecode >> stream xڵYm6BH]hz_4=l]z5,k3$%Klo>#Sޢ.^//o׊HsZ-F)q\ |J<[8 -\uBQKVq93Qv!9n望 b#KZIq7jG ^E Xj=Q݃ceV@p/䴀$ Ywq2t]jn;0vnsΤDM^ԏ<\Tƙ0{ĥ!:6YUظe -;V]@XȻ?dh+苦KǍm/4Q(K\_{vuFuUEB6KEg:֚X͙7{0F2$(DgsmN԰&dSVyuތhWǜWdӫlEZ< i6jdJ#q=0δtrH!qS͛PwAV& nwojxs[sw˃XڑCܚ:[[muTX(Пmk"ʟ%҂7US1x~Ոb[qmv7*Lhv1{押Z|=bt~t8mB^n% :Χdȫ #@FRۑr\r;̑]ɦ>."w] dQ©<_yJpgK!'lιmWu*Cf*5[EU63g[>wd T3kE89w@;Qdv]=P*qy$RìD^a~9>Ϸ#u$rIa~cld]+}Zqq*Z}FO˜XD򥚮 Qm45(g+Zn(.@ qj!!vil#'Ad_X B4G ! 1с3ffkWӣNtw{:@f72Wrn,AHY& MIg23D&NuhtT/l٧~zs}8A;k@xr_| N{X釬Lb Igţ1j@LhCC=/7SeCUۻcY_筐W&.7S67**XovThUN@m蹂0Fx)LbmVt6AɔB'm)S'vSX_\X+\fNrGyƶ1} -GӨ:@g!H,=Z)T)IjrJ7}D^Pε1ee{4V^-iHRdI]j0BVڹhNMa>΃y\޷b U4{/㦪~ThPXs_#!r(HV$`F %:K=Wnf:p=),?lrչSIԥׅ-E \p@=z*(ln IC8lpE}mX DVƩ->dL E}s0d{I"R8mP 6At|>K+`,kԄdI*K@.ۘIRJF;77ԳRT#{Gi GF)8eNDx Z<HQIbb*,3SO@;S(o?,c]՚Pv%C'Phtx+S&`uU>F22[ѺBQ RȗC[EzVsjj2rCMGVcK7x3ݪgB) z^hޜݠqomO#QOpN.")]|׼ͽO)muNv |C endstream endobj 364 0 obj << /Length 1241 /Filter /FlateDecode >> stream xW[o6~<!)@s+.-Q6]\IN,ٲ%s9_qk`|>:-CܘY671RL 40M%~|zmٝ6VW5K&,EdfS&U2ͪ X!P7PߥYnM/S Z>D2/ -)wQ-tF-<|D6NGmX\ds J:bh9a P4v{WoC39CcCx9C7UטyE}<QQ Ⱦ#.39NɀR[Jghwulžj*\SjӬ:xP/`y`0E!n2i^5uS gQZ@+,d*zQ5#t b^{!*@if"9"Nwږ5&/rhZ ֥ڸi#eVuG>4&p.Fa^R,E!QmrKSl!&*iC~f W_Eux[VYVC_-Z"ߨUk A8ˢGA_]u Tj{\GS}X疽 r#aҠ'?6G3CL9$jJ~lV}'~Cܣ, -Ӯ,Xͺ__qÿ{0Oee9d )l>ق- [z'xvl59PI-Un KY;|pBHU(G&䵾1!yބz I-'dg5!em'MH%o'&!c:!7џ; endstream endobj 376 0 obj << /Length 1260 /Filter /FlateDecode >> stream xW[o6~ EbfΖ-EAK#L\JN?~"XdC [K [?^g̱BzԳf `Y˨5_.9NfΣD(Rj_hw]﷩eWtogeV= =l#/p4V%:ƶ EyH,sj:D6%(X Ћo?o?.$_ 偷fn&ݼkܲRcZamϳ֗"YmR^yBP-]Y!ȇNVjMo{by$@$BDqbo"\NQz^hLcrLQ՝$Q$u`D3R?"ɺL!&tLD0zT#P,Zg},3QVg^lHyǻɥŠ,za1\mQ,Jz|:{GHuvpX؇7MakPey/c!)*^' eZ)HڨSw1퐴6p)^NrD2 up)ukn)13dE)E8FGre߮78pw)|e*])(e/9&5/t śBO>h}r0qEwid)2!wzKe]9ѠWӎW4K|ʅ(@);חt"eU{*o&O:`!{A3@l8P4<$]8(iKǐ9pO©4>}{*|SjR.iL^T-Y# bU?z#`5a1/[R8{j/?W8EF6>YҶ 2bk#|h$+Q OrI-PU?sƴ-5{:]l+= 5=riosO\l'pq#)eWh.a+~ЯUޏWΑDr)$s2)$i-V+.=JGL%Wb~O |&'(i 9YKN] \ɉ1EE8]"D>>E zj endstream endobj 389 0 obj << /Length 1052 /Filter /FlateDecode >> stream xVn8}W @͐(QlR`чm>E@K-T/%u~"JI-@Ԉ&("Doa L) P:DYE#U,?bبÔ"u.,Vd3ʳj'|^O cw+TL,njck'upa~)*"Nyb7>$Y8Fm2)a1%Xe&;5sHע)(QЬTK\[¢Z:ȹowc|9cAhWRY[,e)u-'[d~KT=:pcKð[2[oLh9@aD]#'k< Q[h5qm*)ZJ!&^^@⮪Mc5i}Jմuv=뚛m"pXbÍԦĉh: =EJ./hgA"LiNZ[c{$+d=z6]E@\lJ E8RN_+4GPڦ,_3@';2.#e"ip&O B]ٲqrnXp9SXag<=J8ţQV x7rMUjfz:=`~z*I,#w`h(R> stream xWnF}W@+/A[i 4Zl(R!)w+[[y1̙ٹތ^MGG'{ JBz##C/ 1zӹw>!<脳Mp:LOT9ZU˹la >k\YzP`.t" cn|Vz <_?Od]*"O~2_NώAh"%k2c`|jTڃM?|xͷ"o՝gNQ׵dS@wRT{$( (?ӥg0NBV~@c2VE*3'brZ1 +G^!lEF=ġl*(ZlPLi} zcyEUMx"-hV/{3". qnRW:c\U4-d]C9ٶO߽q` ǰnYmSlw1x.^&.oא6΍\]-[9#U2i ]!FS2T=,X* endstream endobj 419 0 obj << /Length 1575 /Filter /FlateDecode >> stream xXmo6_!(&5K֤0Xb"e:W/K_Hʢ"N?""ǻ3vn켞bԉQx8cDYF>&e]-PnW 1LPnr c`^B9\|xʣN) H*YYx}e|c@IYl4ij5z\]~} <Xx VۨE?.3_7B-Aw/q+ѳ5ٵ5,/uoM#* Hb?u35 6:[_.gga|0s|v $#G]2wt9׳?gA qJq cKZ滶cuIˉr\=>ڟQ=T 1F`򕨓*5)xt x (~\nE W҃\4r]MYA՟;(D&&ENuD(bcc 9GTg15;Nzց E0O^V$F*1ZCDʌ# ()x2}D2}nW ́<:8"ȋOs|ѠJݔ9P! L`ٴԩD^6BoLH?D'sHfL`B0 l*c\:uu9|Jl ES;aUʰ 5ױA*9O}MSŻwU׵ҊVd$Ä0ZݒDYNZ!h` !dUY:N.Ww9q? `c4*mTo)4ɐFވ|WJ3B&ki¦@YZ7'MvBCiA+l7=ߛb3B L4m;HyJsjN{hN#yʐS%2 }PTRqxM<[$NYZ )TA4RöcADhlD+__Z |].f>Ok-lki3 !RѲx<}WF;dLYڈ&ٚ;Xj"BG}+ Dd*Sblzs0cV+v"#ȏ$ uk5>_7BXkgW-+'&K> stream xڭWk8=1p`V%wzw`FQl%ة-'; Boz_>zևo[ JB7s`L'.xv Q(%U`hLK΋7P%(wNWK,:<]QzY>?Nz l. |sS%kƀP T@DN@2E|9_>NC?6B!3HIԯU8HA !O_>Ab {x<$|{Kw_owDcLEs~r"¾آ*KA||`傭xvJ=-\7f(~Aې\v}UT瓞rYBѯľrtD6ule~?5 U6 -y]eY&N$w6O0Hjzi@:)QN endstream endobj 349 0 obj << /Type /ObjStm /N 100 /First 879 /Length 1710 /Filter /FlateDecode >> stream xYo7 _!:I(: l@v6sm9wi΢61PERI鈓q("ZgBh&1L2A> ^D! E18Ƅ(O`ك"YR0J\b|r|J &$+D`PI$ yt!tR28J`D$h Htvh D$SA/KI)!{hl7̰!ME))({{ !XD8V,θ0yBE;E!x+fQJ"*!l ya]' Q3c\>b~#0X>fR \R0mða5gJh,tLawNE)`SR caU/&V3r "CWHk`PQHQB@IA)`D)`U =bIi]AFF{( דySj%fƨqגY վxBa?i!Ͽ693G[ rXNO`M`q1|LyН<׹T<[u`^'y~T/>x1}NG]=}}2/$&b ,y\vr /*M7l$M]Si)* Z5g0z.0ׯk~/iܛa-Ow0樛x>[u}fi{/aO5̵.cVt5^wҮCtOg㘻T]bgY*j p6@ӵX;9x;Qh:Lg߼ü:{}Ю;=y$٬w6é I,B2./,;#[U`du`lAXm *\_bUB p-_kp}N{Wm ̻o6P8l5WWbՕ R t1DT fܫ)MvI7uվ~{`bX(Zr NK:0gR''[+NO 0G X⷇V|9ݰ iAbƊd{LJhKCGy_G܃тMK3X;oӡ=H<] ,ElRa%DRH(ԁU*.X/2L9̄W+!{p.c7;FMwjwuh+X".,Ղ-)h+!JΌSC.-v: ?T"ԫS" !J{vcFAGyqA(+[,'}{>L)&L-UHr|nQv"GAIU&KՁ^W5 VDʺ!#suS%gr8&;Qw7$7֯6t!HZDp!Md}^ݻv;bģY; ì_Jut2]ݞ8CE4T5;mԿ>GvC[68Xѯ{sz10w{m6xp̌z s-ZH endstream endobj 454 0 obj << /Length 2059 /Filter /FlateDecode >> stream xڥko6~Ҥ PN);8ܵE rWc#Jz{°5yϐ/Wﮮ4"nΙDNqsu~w7a%{5LKw~0p="%Wܜ˕;bO@j4EH6B ! dN;;l.VfTm'Cfա@ǼFp6~-TkҢˋڐ rӷ-N7M] T7zA +frGW^ȢȧşT0@6~XC[fK62S Y X-Ls&(K1Bk sMB\gj aL^.Y>[yZP+ӣ 5_e{0Q{wt@7ӇKG,ir__zۓG\Yy"?wmV̄-Jj 񩐹i19{\ CdG:_n8Aۍ<2wC7|벗|l Ю'[Iw_~&Nn6W:44'a:A1K׏jKNEd [(r> 6viXkPՃ hH{lA9T}N?0sM- 8@:] e@]BjkJ:N_zeH]PD6vmdBSJvH=Elbw`TR=1^ݒ0ʦ@̢ (M` y_5MSwS@]5$Lb3AH'3~I=`,Xv.J <(bxIĒط]wP,]_G5r볼4%BP 9~QAQLqyϒ7 ;vPֽQ6 G` `uQn<";9B ͎֐Y_wMl8,e'48҄'`hP|w>+"abISm70O4#MčhrV#i;-ɔI!8Ӯ:M瞵[`˰Plm4YTpkz &{ (947ϱ~ꜶKpn),=ZjHz{lW x~?ao2/,YYd;`A˵Xq,uݛ[GGᤅY7n.⹣&4 e g;%mH~{%xLe> stream xX[o6~ϯR)R+uɰKZD9tq)wx,9k? "M~s\gg?.^_{ԉPYfv]Q |18Թaek Nz!@GI궒H"{Ο ^\t7$P士#)C"˗U-0RGҋPpw^Dg7M'C#4fQ 5bE(Qh뇻-.sd'QڋY6,@9XXT fg Y\\{zdQD#6 /4c6{TZ r`c1fohkK,:elV B GbU+gb+ugPRWY'1{?OWł@VK3s<(q!Yn~䢹eo!삍;>1ld4"+y:O[&iXTe,ss<=pD˩ Ŀ/O(!S[Y՞6֭Y4\D\~: E^vJd^W3և^{y;w\۫3s΀ g_\'yQ<裥C!O{)Og?44"2`q`:AQ{uѤ|j靕ŌS6*`lj7Q)ea[~M"e/9@5 :m̏fH-4wXvGߗ3dz 0:.uy@)&5JՔ7fe~\A12wm$|:^Pشe' ]09PØn Χ@'D\O){i:@چM--#c\ij!eQ;A /{楘cFm+y|.8x(l٣% *}Vm%U2Gk@ZߍfWLQSa^ڃShv[Kٯt13eT{1ڳ8!8@5 t /hJE77 k;KHa1XRӆޠ#gl_/@dгU=pSRRrf# 5>ͻI8*U>Oalglv!V] mضEm=|}/ =@ʊO튲 ot)dތWmJM>n5 M  y'ɧS)BZQ?Zw0n*iMe]4qݤu@ZnZɛ)@溽)䠖2M9;un<~CsDY2ѝf~5&0޸jϩE R}j@IXwǮ+meo m|KRmY#?YcW! {̥yv]}Ppd ( />4PN+aּͣWO'Ps;Z?{g\!Q{Pq|,]~,l6<ɳ#hKZqD endstream endobj 489 0 obj << /Length 1912 /Filter /FlateDecode >> stream xX[o6~PHQ`ЮI-"-&K.%q/%Gv=$/w7 :18a8a''7,o/.:c0Pm`+e ;s/W(;/.u"8ZiŠ啥3f8Q G cweE>Ҽ{iח7SAĈx(|YbLf/1pWzU,}o?Qvz,js˲XVT~,Zo==ʳm1*ʾ. 0=?Yxu6*^*؜>=8BR4< c4O!aƨ3[SSLl&dGXlE}uu,Q4#^_]QIP!C! ޾Qu×i](lr9"w^*Ql`q c n/Odar /p{"!MLWɋz%;xP qW|u TMOj+M՚fMJyNc!!! "+ v"JҬsYW"Q^UWBr&2CMMy,$6=*tblȲ0k `++< |}ۢYn[g/OI6_LLfքElj\ BgY|SyY_ac}a{Cfa((T.B#%^7Fj\ZOk 2^F5?][XdwaQN#zi: T]XЩ n!Kv]bUVH*e?J/5\PnA玻JE~nbq-oMAq!yy~1<MǠWFt/hMr§(Qv3ߝM}v{d:}O![ش40 |vAUQ7I> stream xڵW[o6~<JubI!y %&&Q.Eqo(lO(ܾCwu9%AW}/g^μp>MP:2: ;;(Ĩ-r[ey2!|4tϹ>~mIƋ tR8D rdX*c~!AOv3/٪h/zHn@\j-ӛ~vXX.VWDxIB?Fݾ˭c Uv"⬯ͯodQQcG6n%sEnz8xynKuh?$RzgZz;ɂp[ ;A!̉PmaAݚFКRۣ5CN5#iaKrq~ ~OelQY|)fX32Aքw"no<YA z^QP^Avt5}!;1J="'FN?AfS[+'BțyN܎|]D`A!g \しȒNO /qCKQy3=&1Q 5HsfMOI҇Lo(dE0 'vzԅj'bC̄{[@Duqu-D3/^F8ͽβ b &TZ*Fg> pB'tYlRR4:[+4W j=`a^3zZ4ʌܻMz ^h(U,sވ|yT=U+_nn:A6'(.aKKO7=f a`R;KPԽBO8G“Ħϔf$xG0z]8hφ?E**C+=y24'l!׌!Dp+Di j&Лp-Ik-hpCmwXYc6#O؇A{|/"3Es M0+uz$1{`*ɖREB/W'd< ֟y]K  I:EH184"I: <:3  'RWb#8ek9Gj1"+at є W-64k*7hA⢟}? W Q$e(O3k 1_fVObuJxlxK[/<3O41-/\zW^qimc0e#iVg ^x NeL z8ݣ[c endstream endobj 518 0 obj << /Length 1325 /Filter /FlateDecode >> stream xX[o6~P!)R@&E`h/mm %W%ߡHɖ8= AcY<| >LMNΙk $:gO]a6;PLs6E&(A'V|hW"W#Vj]O>wkA0|e@s{(i#祬)sRgɏZꇅøߗр=A_^p;V {{YFE<녴 W+XS%fl:v * -T+qI c~P`K>L q0K9)r1kw.e&$B%ؾ0E :gP V_Ɛ XW( \ \xt> stream xڽn6=_!dHI l@C@ -ӶV]Q}yUUs=wVi]41kݮ)od65ֲR3#ɾ(K i ϊV3 < YF\y}= ޴au-`YR}orI8VV#X8M>eaۡ4k"莺e,Gm 1a-@qnmUщ1 &E۩m8˷}.VOkP'EP@0XzD*jwtB#TҚۼ׎-͙Hmrx?<.Q[\PlFJ ,&Bon;¥NJUM{?KbkpQtbxLJ XHP F~>]9` `SV3Y~. m#\K͉ɕ ڞ8㟁KЂ,iE"2ŽN}&2.a(b(" hq0BCV_ZH/׺ka?`, NGF2rKФ-v^HB;‰"gV<ޔ$JRx qq!k[Gp޵MI[zR,Jh"άVl0;8G1us?$8!!Q4_{@qx{ElWCʩ1<Znԃų=x mD#.o/:j)w o17oLm¶){N Mi/6=^@/[ l mB`&!2 a-+0X5POH'Z5Ц7$le:E 1=Q03m ^԰ɠfl;xҵP3Hb,\ŚC{hߺ-mSRq!DSK*XwWvlc6h+~鈡m8 F sdPSXာ_4BZZ_Էfe3Py"?8`gƄ/I1d\6<%MD!7d%ǭ'dbB< gηnڷrGrO\ ?2T% DD,FmLiP4^[wX4x{|o/+!&,<_i:!-RMZ1L]7|T#Ͼ .(BCV$p4IOdTeLCLLSdjcZQ?[+?/ XH!ævy'+a$I42K;/m-&}-6?&P>Ƞ p2Hy($fOs# uqd>QR:deڄҗ46"zPAI? endstream endobj 549 0 obj << /Length 1821 /Filter /FlateDecode >> stream xڥXo6ࡘ Ĭu@ņuCw("3P.)%u{ȍBA-@۶Y7wn*ax"OO8HOݾmwP6WrtQŷ]gZ$Y%2ؖ0X%e~q]ީ}հqyIոC%np+Y2jc0qHf/6) q=/n4kNMR]w%SՆ=Byfd}TՎ#bA GәD*qI%et$KvSO.q`ެ(k]hzS)#j3һO(EeaW* r:) ji?~8`v8UFIeK)*],)Jw Aq (}b<^A2ÄDn' @ǾmOBMn㔩W1N64kM=e BSK6d]~~5PaX! ) J2 @s$N騎)zp͹ d%:L=Z&{7[@D&34%eV^9sCqm916q(ZiƟ>VbTq(cJ9)kGf|nblɞT?%Diqig0mŞ+KYvҨVX~Jm&|V)h w \d7Ӡi@F((xݰC]{QӧV֝ ֤ȕAِ5D(p2''20;. GP"loʶm J:_!QMɄ՛Pan-V! BqOf NCH(PlWD⻢LCO!'GCMI6E"b.;[C3Ij^-Z/Gkt1TQ̰+d/d DP_O{{ծzkAi<ŀӤѺөv[3g z^MIu?H pn[jeqclWi Ci@N7U,L/ۘsY*@[&f1z؁:R!Îx>ieH& l{حP> stream xZMo9W\, v~xrhGYHI<~_Q-DZm&9dScU%2{ Ѡ*SYRlcy`s>$'+O@#&['B -Ƞ Ft8 IYr81l"̌lp5*Ȇ:0[BYz a,#O6m/l1dSHa`ɨ(er48TÖ`VG a #jɦDcgA>I2"&0#LU1eQS _%g9ѨzNj9iV?irY pf-_`ƓBTI~ QJ;{i[16^.gͲlWT?Pyl҆=ZaC0Rm܋ٷB`Z_tx.j H lpuyx|vmէg> k/8i >i ڀmJfkegՋBbNm7ym/W^>"ݣHEl:u6Z׵eBصiPV@L3>d 7`*7{D@0Y_'BоPǺ6 t'fʥ@bڴk}ӥ, Р͈I!Φv:'lHo] MAM7$@*~k 5><{I)Ln 8/ۄgp{s9[°{Bog{c&۲{mr];0^8,{=dIA~WqI ߫'f ~{yv2];loF" /*r_xƲgDJ[Ѣ tJNmSs'嬞W J[&^JzyΠ[QLj<py}Ѭ>9˥ݤOrKd]x`C endstream endobj 562 0 obj << /Length 1220 /Filter /FlateDecode >> stream xڥW]o6}!)m@$2-Zm!PRoX~.IY9q{/el-l}>^QfMf #ߥ$ٔ:?&קWe 8zwirަ2~@-Ҟ@ r+ZV k;GÙJ ~ycydlΒCN|ܱ( ֳ+muFvvE(L [I!UKf¡~,w4` DLnݞ2CչE$UY'&mlinWP>kE084Rl\ l|.zxVW0{nuż'Fm{q"Ecǿ&R8_gzZ a1K`{^-EV/:tȑ:; 3:j]7|)Lwʣ;O[)/6ڣ=yWGxZfܴuUM \s]/Hi>LV^9#JJ@1 vƶ>U3L%WiA}Uu&3fKa&/Uf.-VsY^:$wfz$bKȽ`5I s@/P MY22k#J<(B9\CjfK˼b3W$U~.[T-2t7y% h*f `bLY^3/WˀG'd.T{gsxCV^9 eHXN7}Iݞ ds DyMJJfݺviQ_@FB(muz*ۑ22mL8;+(_t0$ 4OsH J4`A;u2OAvV',Г&;G* _G B>f|@Ae'yVn"ޝ+kCPŵ( *Ww8^zܽB" \Gb`lbk4pD-~ ! ޽̄62Ň4enJ+=VEpg[K[ :̬"2ufX&FnB;_/_/­g=TR,Bn({ߘIRIl/ٙ}bv azb"7<z[? endstream endobj 579 0 obj << /Length 1399 /Filter /FlateDecode >> stream xXo6B@QLf@%Y ml1Q=\=fN$%KEmH]ts6y58ePPn1pԙG4Tqj$?ٛ>f O&:MN![6"FH'?c'7lџl# 2b'q&O B083$Cb]5 xqiM|*P%9 Mlh{|i\*HN*7RyQt8SDB_^v4Uwu%/b[wu2{;"8Z5h1ƪ_v\Ȍ,m,-v@ϡQi*55#deHN]YO(-2#@ū1,K"tܡOIԢګJ$B+p{G-J9zW2O"áfK|ug E2{WqflDEyvP2> stream xXYo6~/# vio$0+zW %ei%y{<Cgs|r|GON uA/ЉB18<)%JbGӬOQ/r"?%pܬ;8 "hU[Gǧ` 'G0B)oQ8~Zh=RB/(Dj-1ߝYmB梉#gV Ԓ?;R0w7.c(IwNI;+x%+Wnc soFba].3E}(JM^ɪ6եY?Yރ,>'Q|>!({HUW1ri77ԯQ¬ 5OfQp[Ooa-J#AX8;?1T#VEvTfAZI*.k6o\»+YS Syi?!eۅZVMHܹ͆22Y0Ǐg􂷙p|'&BAh\ 0~ibLSV:~lLF@mS+կ`7R('Um_%s3cM;8.[%]xX Ьy3]?V٨k*rnKM:G>JA7[$ȏ{jri_mY|dK()za5k4p@Q[eÁqd+`%+x|tO%nT}2lRYmoUcD`6:Ԭͺ2MȡӥF/6P>Bf C g ɥa&6Dq :vزǨ(ŅFՈ.\8nTWn;vLLJbJ}Vf$$|F'& ( klۏYj{Ұ83+>k"mhy44֬%GrpX䐲G'GX#o5(,>~6\l5iPȯU'wΏޏKHR Oݙ?:@d.E-.m΂h,_QV!GcӸ= ! ƸˮRf&i5\C2#E>M2K~aLpwHcG鎈=XRl l2(-Tk#w#yYܔr%k5Q{9ް̹WFڵ]8{f›}c! cAiswH`楪M$G,H )]f<7{v:FRo2Ǹc%ܵE1NXAL F01LBoh* uNO?bypn*O&ʹkba^lUXAY Q;Zs/ 2lԢ튲ZfQU ᱥ̸BN3u(/<_%o*JY/H/*h]6ؾbX%Ԓ< [!!;F{8bbu%tXCKʫJ]c$I;ų($@PJ1-(em` q `kv=Ő ݗ|zWp2_k1kwG{hpN:xhjk5Qk#[4ϱaIV + ur3/uA#eGlio}ִW{tTm\$˝3[hZY9DFxGu endstream endobj 604 0 obj << /Length 1185 /Filter /FlateDecode >> stream xڵVmo6_!(&3KJ^m%~Xw-16YE)w|S$CH~4 "oqQH#/0ba-r_j_ 這b$i) w(xfgA̕ߧi Cm.\Wu+ $rvrYgS0t$9Q2j*('An f~4$CQbRCsXJ4ƾ?-*7|k '4Dڳ,E־lh1%?"Zm7 HcA)ca&@49oX?"Q]1rUq}])يzZadE0[eje+ a?BY]ݕEJsYno]A,׉5-_gf݊mZ5|+HPIo|ky5yjd@lh?-er2~xodϽ({RA0zcaq}Ŷ+q,RK\/& OxAe#(GaxuQx1-Jv٢Ԡ<~BDH(>A53<4S}rfԓF-<גh`]{YS|GaY=m #񿔣*0 wcmSTkI#" 'ZqJQ+cG:ΔӰ朗QGݘvc,Pb`fw}ӿGeD@RK^BɐL]60LSWo_':.Xsz_?Ύ ܎ J聫+F@CyN>5;ge=) hZ endstream endobj 616 0 obj << /Length 1425 /Filter /FlateDecode >> stream xX[o6~ЇC͐ԕuΐ"EtN\jdUڇ1EGaޯ b1# $( Xz/}O_/Aog3D=Eu-5dj"^7%7O=RNox!!( һ<^o!!(e8sMy]fVExKtF# .Poy3%ɢV5hs4 FC@ /\MU8whԛpk.V5VpYՍ^n˝]o\b64r?# ?bX2ǡ{шE88V iQLZ%)=_Ց7$e R;YH#$!`Nt2;Aw%&DI vo9\̟_͟,F C8~qy pN<A)$:1 t.ŔF; /w82JA5b--kaZx`:0s5u`d]JL|AL:4~ ~9P0 i5(Gޝvgjno"\ƒI>z7;< oBO~;CJ x?`@ñ95?+sjiH&pa,[YMoM&|4 hFbօ1j$K!#FcCGƇ8Icvk7o KFX5H1*AJjnC}$琟Pp{xb>ʁѨOsρDL?BոRw )@R { W C5k#)ȗ|ٙUȷj1J(9g)$9)a?ِ Sp.$[ީ71( m1LI?<8 .> |9%6[)XH%q g7Ih]fMfެ,B7fo0ҡ|+i^@ 6m-Dal쵔[CoM:E"{0s>- fh5xa\vj]wq*vEa0ȢɅ*7SVB>T]ZN@[^7ԡ:s'Zm{Z Vtq+RN emߙM6m4Z9XѡɍZٺ@>g$CA=X΃E JMjtd2ݪN#Vn[izo ӄL6rcs} 8/HZ[?=+ ֕qUCJECUC7:ߕHJw/1N]$L3J 3v( endstream endobj 630 0 obj << /Length 1343 /Filter /FlateDecode >> stream xXmo6_!(j5KJԛ HS;I%hb"eѠGg{~GRDGI- sGX8 zo&2|;c `,mF07>̢_o&m JtH#@?TCsrQ> \'<- m~fbjI" {=6l}Uj=V%uc\NVh0nx&c)0S3l]f +N.ݮ -\jAՐ 6ARd3PܿMyyv!{K5"UrˎGLp%m6|Q>DG?$&ֺ2ͣM=zuvr?Xv7w c D1%~ehl~?_ew4ʾ8 A v *^D簖xi7 |?K(|9N},ؒ?[BSKUoU*wN/Ǻn._ ~h\߻( Қ((oZ=U%ز'}]LA/E圻~ YhX%+~19>!}m|]W_;4e +?-ªN_> stream xݖAo0|DjKi["K-(K)>mpz/f;U1 m3#D> stream xYn[7+l6p8CF<@ Nm,[IR{Jre[m0|I\Ι(e"Wsp&W%x&`82q..3zYP9xOS`ju%L6]6WbsH&G1a\]L> kA1+ D((9 D$GQ #B 1Cŀ+C?;4 CFl[Bbn(*t+5s/ L7a>H%Vu)3BD#'%$5U]*,1ȰF-U*F83 dV1T)vZ $*PO+Effڞ'Ph)5 V5 V/$枉TL "%H7%NXl +2_B\-lORSOZGI,Kbg95W%03N) v&ؓN 1P`Ӻ 6I PuDp:bϨhlXzoy6gݬr9k'Y@HO͋Nz|ΘЇ?X0k~ȟClgt>/'KwaN] b}͇÷۰1,<|<1v2Du\=ܽ6Ux<s̖x1<-"xv 'N_s2\s5_gZNF)Busx>9Lt.:?u!yї :OEq _S^bwYǮ }t\# 'n`Xkn` 7N_qՇ"M! Q:x.ϸ|υƷ?4;R*-adp_RʌpEr8LxDD zc$^E|[i\g}zџ~WчQB j>4͓1hV'y+_m2L0ˊ*(c]qDZsy6VN&ͻo$ $oz; A5_]'+oSDĞ7:V^WYrG+eSBc_ڑk_rGpn`|DK i4S<>,]1a\"=WS+=㖺( vGci^DA ܀W%Q{ܵkI\qZĝP+|^<_D ]qпjꞹֱوۤ KZ:'Z'0%~a/߼9i@4`_# s; e^^;eӧ"uק<ݺU۷Q۪!x+7ŚBעH`F Б3#mvr7pJHe>.܉5S=ױ,uXw> Ѳ;+Z;wD6o^$;QnZ퇃}V?F+'ravq=zױ;&E1[T=8˂(BPO7Nrz`A%% endstream endobj 654 0 obj << /Length 2690 /Filter /FlateDecode >> stream xڭZko"݋+z-R`---˱zeɫn_Wؾ(AԘ83g{t^޼U’W+uë(tY _dxl?GǢTyS޸xN^ y=egA]n57UMMCo=g^L^$')^]i ]%AH3ehŧfR@o/TzJW'|roV̻Szu|6c* :FgӨGhP) Hls?r)+Z*Tqj!c̫!Iel1IGNmɣ䢰 {GηyD)GKS$_LkUž]%̑ p9ET^DNY4ՎF ĈFKBȏ:0 u2mH"{jg<|:Jk`h 'l1,tc'Ѵ/*>h`v-30ʸ@c;@ŋ^<>7~Q;Z;|SV@y dpЊBY'3;k"@mvB)a#]r:2#J@'n}fDX ɴV IjH\p(-".jDgiNh5= Ue)i.%nH?/d L@jbS뻾'<'4JLނQMb2=Hy7g2@Yِ*Ϣ`j@M775ڨ^਽GtM|' ʪTQI;,t;O ]aO # _#9h_+͂] i5$7Gz#l=1nյ{򧎬EwHDUWY^ԯ3 /g%loHGdH=E;'…N!ֽV"Ps:OUK ~ؘ^DVs"9篶=*HX3* )"oF>3n?~7OPg\15qQ?]N |D@u6?b{A lyB}/M^S?sURWUmLca-* oLX`IAG <n( ݉pY^0xAamnD)0͸We( 6rgub#5rb6䧻It:E.eWhE:(Gn/"Am_n|q 7QMmI q!g،~fb<KcLT)2eVgߕй'z}LEo4v\QD: T/qGb]$]:qC([e[o2RM 0yj N\`3j?sgӕR QcUKO/!eS6CY̿/Pm=[-ݲgxa_u/9ph%T9rN&a4YF]ƦGrG!ƒy%|\o9e`ijPcIC)ڮ.ϟ&b endstream endobj 670 0 obj << /Length 1779 /Filter /FlateDecode >> stream xڭXmo6_y(j1+RF[ m CPMa D,9~G$K8Ð֭e[ >] ޝ;gum0*nF=qǻsH:C|-d>[|)Fkl@-gO|D۹o|w+;= FWK^=;Yed5aTGGSWvXb"eZU!gr҇*> DD|zE.6c>pexQp4_qҢ.Rޡݜ4Xdeh6|B*u&/`Nz+#Иb|f> SQ|.L +*EG.S$p)2."UGaR=1A͇xoB8:5^n_<{Lb2<ڛ~mіiK"G~֣]Z.$̬H9֥[NؖVU^F%x˱`̃3s^"3rUcA&C|*@H ~>m QeRnUUH(pvo{X = ڣ )P ZhHhV3#,lLԌ=uxxם "͚zAu'œOG0c\4iS_ 3? S30eFEwo"FBW17(g6T蠬G1xð_ /q*7q]tg̱"`e"j0y fC4;(Vz+X=zKm"VW2fK|~϶Re!#߁:{jZC#>m-YCx[n,hQyS3GM|[n`Rg0k+O@ .sV -eWI1߃ 3L£Ofmo# 5vKu>:lT+ e_R~gԆr!OC.ZW*ӑT,qUD^q/bAatC))'Xf4EEV/s,K3$/qjݮ]H$:SXR]|,WA:կQ_u TџcZ\m4VR,wUS}HhwKZ^-LLZg;0n[LRR+J3֕nUJ@=ߥun^{?0SyQp4vGQ SZذnִʷ̌ hDY՗&M:3ׁ!ȭrX+hM 7ɲ\"KvIļ UܤjRd:8DFKbO;мTq|?_ Czd"I-" zVF@ULJ(acPvڋ[fn(汝4rS1о9٫ f4 endstream endobj 693 0 obj << /Length 1293 /Filter /FlateDecode >> stream xڵW[o6~PER+uI!/mܾt]@K#Td~٢cv!@HSƏ|{&/&הx J +>"4G">Oˮ@+O/?/ Q(Z`%2[y5rM!GqlrHu` '(UK_>M$a9Q¬\͙O_X2[ޭ$/}4AvURˀ2Y~:*=/߼4.%+[' CF fζMl:k52cqY_˺-8aW\B Y1bm!:<}zi92@$-ZzC:B^Nޏf.)!cq| q(q_6oDS3O!IYgiXL2Q.jq`A1;]rCIei:X{Dj5V^4j4tRu֨tUKϺ/C'^?!@ IЃlIPiz }(WmL{W^u-*!A9ĢКaHgXhBU1˹XP$PPh@h)1yeF|gIH۫ΐkl֫y=}c&g!"ki("$ڪ8y'dnIT2E5m4FNASĢ-(?+)@4,A'eM!{g"y)RTv$cŶ0/k8 )u=-L<wiNg sh,c#XA2u9IyU3d"3!U> stream xڝWK6W>KR=mhH[AIj%WHj7_Cd+κ93~C;mfq{NfNbxGףϛw7hGi n#eS_C-,..vL0Z|6:Γ){bt.!>q""?ӏxb$._ic0Mk@0QfŞW&2^h 7;k # ~ĐbBCzx%j\=hnP(ꌳT1PwPI;E'a:79{ Y[^)$ v7^Lqч5J5FPd?WIB?Y{|gQg)ON(IcL_,oڇwC/;˶w`z^D7 4BdpqQ$gkRpUPŬA]j$?ms!Lϧд:<' T~{C3Цs>-pBr=FR?qU .v}j)-tamBj8;0f+Фe~e"{eO!A"\3 A8$kj:c#+eF= *X(CKoJr0nųkc]m@h:>*̲koy8W>i2[FYZ@M:~ endstream endobj 712 0 obj << /Length 1567 /Filter /FlateDecode >> stream xڥX[o6~ϯ2E] lCÆXW{h Xnٰsx,ENER<߹}<ǞsxO?l.4#gwDNy$dɝn[нY"r.wݭ>m~y'%qB ψSk0h$Ni+m{njCϽtzr J45I^cʢg֔Ǫc/3Bvd&m .&*!~jAwm+NjoN^,tyYu?YkzYv,k3nEVw *=<2.#xl̜7{Trs+"D-R6IV ݟ:' |Bh´YJ$ih7E.`8pɳJfMVvw Sw D9hgöN}Q.4ڏBH4 ݙFi[q :YmKVN`.0"xFr[.g=@\J _S#Mc1R!h6j02 5jlhnHuy_ x/h ?Qǃf ݨk\웶kEx׼mc6$! \^7Mc;Dn}fg%C9lifn`Ëبo㒏wEK++ gMY|1ƒ(#=N[,c~ZYSP0۫e-ag/[4Q|cXCn!!L锫^}U[eZl-81 ,ymiꛕzpu_M_RPom!>T!4*Է~xgD4CK8VDa c6U\yP&zc!^}+ӢGbƗo T{Tou>1ʨD{`>u-A'2[ ;e%%L>/]SEk$NϾ{vlk!my?*_m.@:4>T ,.>|>aPZߪ]wgcYu鳜HUM z՝O:!$qn LXۀ&h@ʖ@Jߖ-]&֦X.'%ċ%v8LYZNlj:k3Nut:O=]t%ٙOSbgIZ̓ՠ`?T5rw_f䓐F_+cgS(ñ%/ endstream endobj 722 0 obj << /Length 1989 /Filter /FlateDecode >> stream xڥX[ܶ~XI xiԵA8Hp[>Fe_!.qD o3o2XI DE p Q;ǶߚMb $?b}|j6>fy$1ZZڙ k2cd#-$Іo e+,L鎁cxɴ15e +[ӄe2Q sYU8cMv%Y7UB(Jxt^F ШDd9dDC'yDTi]~>(sPe@ 8O/gEV>Dg!v$ Eb*M#Úo3Z}-&l34)0جA}I1OBp, qXו;®t+A# sF.KY'hן} qW5㡻ܨ? &aLB/y?ߖp{)5<.]ЭJ A+@lP{.1p~ƗMFC ;!sDOV.y\_"++gyfS4pK8!-6 (I6}I'2'6~ DfYYk7 ˶zNa,%Ԭh_ R5#[zj)SR7B(EfynKJ4%$~{O 1!e17 *@EL/:#:Wr 溴6 KrM4.Z7{߾f>RZWmsO#k&(!Lh2l?/&E];9Zb :]Zt.Dx9;qal$/+[CK09dfݢТApc8%$WWG M +R}!@XJ2:' KPz~tժu Ѽ>uuk}YPgMePz%o~?ɅU^.4Uu>+'K x/ endstream endobj 732 0 obj << /Length 1434 /Filter /FlateDecode >> stream xڽXmo6_!(j1KI[֤>Xb"S$ѪPToEI?"wϝwWw^֎1ID!F9ܹ}Y"~S^ww>!EQ G OgGn,HlU2e|-^4n…WU_^0O˞ )K~Kk_losZ=^7:} |g(CeWz]_ R\ȸ( Og io> wM2w%a (AN+)kTj5e_1AnM޼p|$@wB E!KmtrD{:P˫%(gP? AQ  Ào$Nfghz B9I .Nb3=|kcMߪSэN"WOSA .X_U{|oݓ4D!SL:N5~Eb͑P]\pjKHWX'!Y RwQ]_0*GȷX-4ο7L.&܊!U~sf@/# 4)&j=)@Keh{$XNo8S1E^,=tr_~)3 0 En*zZ?lE~IeyXL[Fjv7Yw,чɪgMV ҹz02e`EWjp7L";foJgK3+NޔtJ PڿXiokXM$@v+Z.z {`PV7<B\=#ax(mr5\$y[n>~(v-] T C=d;Udh$Q2+lT\CVlDPpujӊ]K cXYƵD(6랺o+ Kԥ|=|U/^ts!iLV]g둠I ї*F{mh*1E0.."w<0 3f!qЫr2|oW%ǂ0 2s_ei}C#ݞthT0qBWŠf i+̃p8[;[m, ?3EizEYۦ+xrYk-j. e Ǻ*v9otTJ5}G]c5t Za[+orr+)'E})6aU#"m)]XDaZU.":M2xЬLQ`{UhPnqTBqƚB +(ZB<g>6GMf2&X7?\/LߍzmO endstream endobj 746 0 obj << /Length 1083 /Filter /FlateDecode >> stream xڽVnF}W, %[k4b-MAI-i^_ً(-EI.Z| gΜAGo'׷<@1%h@BI M2m$!h*͋&}X%yGĪS1 զ93/Zcb422g1Sᥳ̺Xq4 6滆(&q@Y'y &1"AgR&+[NE٦ǂ[RX-嬩/ftTcFqLS#،yϧ |T$z%/>>%8+[],tEIo oۋm)̭ayDA Ţ$Ef As HEsPCO2iw。=2HG0 fA ݦITKEة*F6&fkG'6Y0 lxKY,e:pl…wR7q3a"35Oh7ÌZd[y"̕Q)`Ί`/|!hMsV_ ( N8႐ivJ ͊!gH{!ȮUqKh,6nSjxj>`x$b2KV'%yg}O6EMf>+mҹYM7=o*[IˆyŽa/¹D]NLvi7T(W0srImN?ى}3me{-%ҺTr"&b u򘾼-4_;ՉCuzC):3`]bDޛ,וO9]O{ht۪+Ymmkt0V|8B#N?{71TLhwkIJUW(ac;J9dU4jjlv5(>KI`ՄޭʦVYo*a1/vؚ#8d`JMh{.YQǚ}3@Y9+q+W Hb5ie^:}3ёK޺> stream xYKo7WBqfHi-P89urm1*Khf(zwἇcv(.F8=V )#\3JL+bTW%20U񞫣PgP(Hdv`/ SG-'GUmUvLcq IJ6#xP8ЄA5@PqAPrJ$'+2&UԉK$58Qm2ֈ0%+(h'H&GЊdkq`!EiPI2&1 XjeMe Ѹ:*6Tm-$V vJQiSH2JĄGJ%;!$UmV3 KTeB1IaʠlSRL5r1T{+rd(d/Af:M۪jh6cz%1u(;tp&?Ou>4U6b3`lSGoIwƿwU0foG/_vأ|7VcdO0.^@brqjv'nGnzRL.7i:_L|4>ԦO׋ID3̩Dn%H ;_ P<⊱dqSGuFWu|voߏsB݋zC2o(+$E W,_w M8}ˋ&aHC\@J14p1Y RoA n!=IǤ[X[73.? Odf~O[Pb6>ulgw2gJLBF0a!|{>M^>沧G3#cԏuP#-~Cl,jHV[f}yz:|߼xpL0D.I-. =Bʼݵg _MO6|WL͜NzQB+EVy"+}JA,}+}+ڏ}[rASb>#$skhJ݋?͐$#٣z9t( 6 QaπW~da7V02~ɍ`AaK(\jp'q!=Lv[)Qm]Z)$gg-Xf8lU=+#RSDڽud-H,hٔ56z7m[sZ`"|}0%5=/ZH52#cjOZOT lw v&h.p=HH~}0S } 1R}ߟcӟ "g>C:P1ro{'c_'.vgRx C|璚9 bak } w% endstream endobj 769 0 obj << /Length 1337 /Filter /FlateDecode >> stream xڵWYoF~ׯ s܃Gznk($A+E>;Ygn ލ=sEzj#D$(kDu) `p6DN|.A21ȿ>_&v0-R J#|Z87f-ul˷owU7#\G<\iLm96H]=hXT#ԓt>lp[ ^Bx쿴~h"1|w!}j_4o$XU!%uU\EaS^Dђ $!Q0D2ZR!uF|[b=ADQYZW*-\(o4^YxbNN@THM% q$b|btfiSK2]D# `RAǸQh4D2`h0 jU<̓$DY~`WBhAҫQ2lgePDySwECf"q;-f.?7.ar0JQoGG%sMEzxB(7D,0L.5SKTJؤTuqx-l;`ݥQA ?:z]uWl*m=΍嫶^_4vF.{{֧7?t4ӧ^6 `_)s@%d~;4BQWBކ ͢m!gjWjIDG髩6,;0]ƒLݢK!?%Ai=XB uoIH!dRyc'c8mΎa_T~9B Ⱥ8PYΊ:19M09ЇHjۦ+ 9K&{]h^_://>YԴsa;dv~ǀƇSЈ0X`/|qLE6  endstream endobj 793 0 obj << /Length 1344 /Filter /FlateDecode >> stream xXmo6_!(&#zźٖ}銀H,9];HdsAxǻ#pwK170/0A>qEj|4Id}Zzv Gw`9Z4rbPMn-M{=tpd8BAzeپsy*:E$%m[mH8=䇸3;2ZCq3eyS҇Q$FTyX;(~FR@c2 nXEW_7ۂ{FgUG%u*Gհ/n,BhuB%CS]_]uiY獅vboBqHOqat;[;tIe "4,ckf8gI[|D5ƛ@aq|}o4+|(21CQn^3 HflyؤMA%ka="IDlS%V֙b~?Yv[v8rQG^ڤcB7JnK ܅A& U[ZTzaeVq_7)D&zYQJ(ƥimhBt7gǜ̈́Ä}8a0Rhv { 0Mh4j64<퀗#mp >Z ӯd-uʵZoWZg?lHw*3暇䚰ܬZDLݔ|FRrAC>r kyQQb@hyQ%zCo;^"8Ot{|^; bPlsPtEY؇Z_"Uvk},vN+} EBfIz`Z]ZƚZRraZŞ AvqOWk8O>F /Z-ZSbf'|JrK "j~1*pUR1|.od'IJhգQ2\;cxo_+FiqXam-vhZ>2^;.,?XfX=_8 EPIf?9F ?AS8~"c+YWIz"Jc>{dtgqւ jЍz"wxҙ&t-2w`y؞G?C<$@J)%EԍrѥwM΋jxJSb\AdSfjT?>fU+@REQb +4\} QWo[ hlZWue$FEW2GŴ+T1;Jm?! oK{5E4C68r յ| endstream endobj 810 0 obj << /Length 1812 /Filter /FlateDecode >> stream xڭXko6_!(&5KAICW 뒢EСM}H@[,T'|)6){=[z{sы(2рzQE]_s^ E}Uξ^{qF&(IaY5%#l6]fEϛcb㉼qAo; ^f Cb\\};"pR!hLQcX]~^ y ٪k/H]чsE.#Jt_cg"Nl|d(_%EmcF=igCx#DMWM',s ȷm5Vm)rRAvM}vgS[@NLɾS!FK0^h6Zv@63~퓳;r6 !Vy/̩$ 0$3~;jcL[+F۪[:X$ҫع"r :1xM$%[+mOB0Pk&;x!eQ$mdO;ڭR9+In`2fٶ /ylwIfb+;k)z<_T./iQ r"Ce1Yx( p:?Γ׹K}<$@g/j& I|Ŗ)fjbx} V$#4ϒP!RǖL#-]@EADalՈ&а3kA-?*&lQn/On6P dGCE9v@u›ÊG;R=Yf? mAD/W]p~ Xh8EfoeY5˹ k"q@mZ? .a8..T|_KB0^E 7(K'rLSGKn[J.v87 m` J:5WRu`ݷ P]Y%4R!5FkpM`%UgZ ?Q##0i"x,+&< UQk*J uGEG~oU[L-}|iL:Sͪc(Jc[uzD ZI]wWm9hy #t )ԖNOUӂOLF@0J`][*0Wлrn˘u"#]y~^_\U+w&QDn禆#M,z\dX+%p)4@q pE3;б$qile! a݋"^tujGw@+-۝t/B6Sii#:@) ג֢0y+Zu๎9oP6uUtWn(&rkκ\\)-X:+) G̎6oO̧дԴxZ,\kzfCFw:ŎZЙ:YywH٠v]F|O'Gk endstream endobj 828 0 obj << /Length 1821 /Filter /FlateDecode >> stream xڝX{o6ߟBHQj[6 mEhXIOKeq Љ:~ucarqE[k`׷#ϱeb}xe 8?B.AiV*YyvB."Ċɧ/J[ Du/8s%p2r7~ ^` pp6BmP4dJwZՒO|T3'l6BΘ8}/&~P=}y~$xJ7MZ=f֜(,7H*Ei3*D@$2t6wp4mlFV~OE٨Mq,S U}beXV5Uf7cChs1|i^[dZt[ SZ+\:'Tw3P/S9'*nTee1x(A Px\}eq@) (KItqmga'0pA'-TW?$E"p; *l{(򔇀XfM-_RřX)+*|}թȘG@ T4(e}$JA Qݞ $gPǦI #ɘLW"tv"u4A^!P+!@"Cs=l0j*Ko´[,҅ʕ<=|YEs"(dlgQb)ӞuaMF~tHМx.rmb?S:WeHЄAܴ9+ trw]?cfKW/޼,J8pfʤY"V=':=MVaMĈ؇bf#8*⸇hx;"QC(n8O7(jc Mx呯^Wrfc#7t_Rqh$ lmZ\"nVS65N_ncTdq`.ԛ"!!w_C1\ EhS=>p\@@w"Q%^Ysn7]Z;ݰ ,L1o¶vsǻJ%ڜlj'b.KɼBgFQㄴJv#Ҋh G;ıőcYӭ6n&Ru ikeՀǎZ%*tT2mL) CH_BYjA:ZOЌr-Fqu|&Qym7BUi4ZVBƐkH.O6IN ϣ$qFe ߾+>r mT׆~9ɁA5?UO^YPlRU#SMLzxpR]Z7KQLCTw@dTW(EU4ǁMD]~z%"~19Oܕm#g?2Ks"}̅03kz{H:;$RPbNY?>;ղR0܂@2?ws`ܿf4{#~]nRfuه 6#cwj|n{Z4FB25$KQB5\.-^.姟?+_\,LG0Do~ _~#d5 1 demKVv#YQ0߷5R_9nC[(4E(vSz?Cl endstream endobj 840 0 obj << /Length 1258 /Filter /FlateDecode >> stream xڕW[o6~:!ul :d9CWxLDu )ц")b;h\?~:k:w)nVx~LBufKd8 .ɜ?%o_/o=C&XKH qyzN$dC_ʎȓcw"qpGqQ@Be~$D-ʍ^M3^/0'@MO2;Z`iup1Ӧ CA0 V.1#z/ֱ4 (RtdeL[e '!&|”1%7<{curM2NlWX1xm0FXU֒xע"EԞ.JuthRݷv, 2Pt͈ȅ= &{KѡzS 4 mr6+ʷO[˖)uv(DPIʆGfr}* 7М'ܳ5Ӳ| u|jdEY)T&T/RM~XΏ :78OL/Sӷdkڕe$fP%IAIS1J{ȷMMYr|<6'3Nm2) ]YE$ϼnЛd]|[U'TzH?㞱_}d4< 0X"iJ ̝o=$lu;1nCujʲF=L m`i"iRuA-~3-OZ-0M'|axRqWit c  ǗG}("pIa X<+% /4{YZfN\6!y‹Y <gkCOPy=m+04ā3fĝ*ϚMӳ IaO./'Dp S|MnT&!dm _9) xaWA.S*Fu}SjFO|۴J'Ve97mv5!EZ'meNz `uG,L7yjJ('K[Z*o$9bm"uuvdtA+-*o/w1mr+ IW͹R)x<pAߢnUi+؆||dKb8S)1@ו)`c֌`ؕo?E =ȢdE*hD3  iC#$Jy!;%g6@pt?cn8ɺ8E27 .qʽnin`GV%͔dEK] V5X2`ύm2;Bjܞ FeO#_ӕx@A̩O^pdfffNI/}y }719$Y˜*WxVVp^3KSp f@Vø S02VK/@x}@ȉpul~CnV^^}FڢM*Ij?mf0Dt6eK: ׊,ӻJ5uОGupBU9@&~; Ë MKm&uM*_Mԅ @@S)K4 2gfH_[z9)2 Nf-q#`{<:FUtG/?ц ]M]C[pxxA URbI'm4) LOB U ]A\pOS,4$X7%bjsp$S4)^{ +f-$%©땐E^U\?0{ٺ'ٔO2V!{q뱖 "!t7io29[@wXŷt{6=4۶ }v&J\N!nĪԛm ^^>v&G.S;zԾJ@Ǣ&| }n>VރU~Y\,휪r"ω 7/^>Qv({kp ;%=;_-~@$Q_wܣUW2Mr endstream endobj 858 0 obj << /Length 1260 /Filter /FlateDecode >> stream xڭWn6/뺛Zj_D53ETonN ?ԍɖVfXnRх8=?<WXwse%inz(}o>]`m7imkH =1zA3DjIb!uAщ,/o$8}3HsqrYmH}qٶRqd+9j2I·z]m ҙw,+%lư٘$q䘁ZN&2nM]Fwu63wKl޾ڍ`}KXeNnm2iNBɂߔTX}}FM'jɕg.`GNoݛ\N.gt\߼=[o\S,?U9u{]mU%ڍ9aj'FU!YYH P(?s<'{|n;63PJpBɆfB.LUzC9nWz5T&D VZ+Fnr!^;*'LUoy%@K"60wdž`БW!3ˀnǾ8rxUM-BR3T :%KUB4D{+g endstream endobj 760 0 obj << /Type /ObjStm /N 100 /First 886 /Length 1877 /Filter /FlateDecode >> stream xYMo7W\(r @"m(:9lmbT\In7/Yxrnaq..3uW9p)(rh1Wɕdl"b$xR xnd[":r1'I訍l`.6JF).`EmŻ4M`T1RZՂM-Gtّݞq1OMR()TQb2LEGDCpHN1 :#dΠQ*).iF&˜gl8N5䜜FdLN;ș]»,C\pnDx:1S DŽQM cq WvlWb4Kr rq,)gd#:^$%I訍d'lZ(chBf_`%M6-fRvϙL_[w?L[,e$8i0vI+}ž75,MX^8,7e{6kZp>7o<1DxawyS(*x,yHNj;BOD6ܖH>>G wوFd7MTHZ!m8ȣ7mHI=4v<LbK"Û-1[5+DLD?+R !|C.c%$y 0H4 ̢>S QkA'aXa`bV#C_U|+] fܮ`v%}idFdM7,ݴڷޖŎѐ={$ġ@I gY7Y}ꯓ#G<)i^Nhܘ'Cj,$j'K-{8؂|fTd?P7\2F&حXL< HA}HqE\<g(#r9 2fb+E+Rh+鍒UVZsdx>cF"]N%Bq0%YOOɪ}A4f,t_5Xl9m^NmF0`VcZ-SA tB +}|+X _!w@̨ d 62@RA0HCAB(Ti?͚0BŎijo?X1ݧNj뱐Xx{ )[ cEs0a`&+I!U*4 e 6Bycy(0,=,yے .&*t .&փvUcyK{YJ"O8q(1ul"{,'&4cN kXKukSvKM|\ a،= Hu d|3{<{y|ɷF1~Yb endstream endobj 867 0 obj << /Length 365 /Filter /FlateDecode >> stream xڝSIK@W̥Ђ3dAJ@6' m,*` @[lAt1mYA^榹x IN"Fu, k44I)#hXڌL(pXMaՖiϽK ɶɇvd*txNrW ha<߇FqSge:Q:-#orI$@PUZ%&a2\Qiˊʐio|dsP}]dO0GDsP#Ag3f 9*Iވ9 \Ka}]㋪}դq^GmG>wVrȟGND\+ut2Hd)i.]JGX endstream endobj 973 0 obj << /Length 1534 /Filter /FlateDecode >> stream x[[6~_c"ulcRV#xvifY,`R} sw=sW՛w;A@f@? ĿbëY?o!P@}oΆA*V?.{2 OO ?:E-G-{!lî QH ˪dodڥ۔ؠ-By' %@BAp&P$· "+($\HE[^t9G<"-Vt(8% B |[WbH=2X((zhšRQ%\ݱ# "^#|AL @-bcX'bR8@F,e6á2M"+(h>j lOٓ׬8+4c^F ȓCȷ _/AcB 8)$Sͱ퀙hKjL/cFhq͞}2n}eh/ T1%& 98~B"a#Avv䋪z[cΧ뾺ee2v,y.=Q}]lH. eא"EK;6|LYLn9+$M !:B ioҪDgĆѹ?wva)dWt[v:@)hY8δ9B18-_qx 'xiخi=H6l[Fwz감{^^2c?2^L@BK^ǥ]߈]%8~(A'E.i!}~YRO$d 'ݾNѫ`&y" Il]|_}LnD:qxNm<P(olo _n䠫-(Uu&"M#+[R<%C1yȫmg|~ yAI%[`z|vU￾mTNiz7v7&DCȁ)!ɳrmBmH(L?\} K endstream endobj 864 0 obj << /Type /ObjStm /N 100 /First 913 /Length 2624 /Filter /FlateDecode >> stream xڽ[ˮWpl8Y~@I0,-Z(E`5ϩ꾒F^\{sxX*>VZNE 7D [^yE[Զ>Y|B* h)衅fxm:BxM:Wpg[avr\p\u"ݭFQ=pB>*mOt  ԢщEs>GNj;sc}BMNQ1Cgz)6ٴ+h!KuFtL.ZiK}$)`64AV@|a,xet:^3˘:f?IAʼn_h0,b0Qg!^\A.HOC禀n_̄Ͱ̈́0c_WoT?XbcHE>ZmVC4|lXS>N!5D\㜿~/K)YjerdY^ RL:H4d~ >k66a$SGн_ |WBb d/h?6>C"$u\IOm,桖!!Ub>:bҮ=I yBdƮ !i~KU?vٮdIGCP(u4d"MDA@ҩƆu"Rk!`)Y"JNdT'JNtTovBckd㐹&] rȸAO+5E&M$ %qHXo$c& 1Y2u~jh wKVLC_l{9SG~%W/KS`1a}FXHaTpzYcp5n1xu1H|kN\78 p6IHKb\8!f3_7|MM93>ЗA42H& rCOpRحT31)6)nr=U] `2e2I0+m`F[tiɱ})-<qG% S:ihluZg qkزsKRH+vKfWYCL"c/YsbP j|Pi#ܳ]}ty2!]F49h 9.Pa5~H@c2R"cYBlũC_۳s8a`/T3SKNq@0*K18ϲ0`߱[pQCr@sWng>r˜y.CY\]{Z#_:/bvC^8qMU˽۱e*qZ a#!:"u6Cln:umc6+u8nsjTml\ud-8 [`tm- endstream endobj 1051 0 obj << /Length 1581 /Filter /FlateDecode >> stream x[ߏ6~߿Vj|ǪwUЇj*UD# }M\̂NO:V`͌x{|ݻxHwLǙ(F}a}=8J_~cw~+t{ax"N0|_,A"Hu~hP?@xETzEꟿ{Q BR& }mik ?hҤc!)4+L3CFDlBԟR2(}yo<IEL+. db̬5ĀL AOAwGχ -7~<€I6) xÖݭ'0*_7l'kNJ)tLκdFAPHTQ?cCo^@ȗl[* G I%uHk$P{iHjOd&3JŔ-qIbS˛M%P{aUgRy_K aM|K>v;)LoǔpysDY ~;H4"F\>1~.9q WQ+DK%ɪ('_xeؚ Vdb/&35DK#y_$%O%%%о%%%B/+O{9/,[lyy;L4P`qYZcA1΢ըݪz*ScC8D,9:G>-*U}CL#%>籖R9G+;4ymC. E@$СE?ePHK\oʕ|hDNZ+ _Q*^/4[: U a5ad4¨H7 UsQPV M@&:ϦzueYmG=)DۧXߪ/4'W>o7eJMR:9rP_ f_%fCx/浮VY YQ^eÆ@ {7ASVyan:.ih!a9y@\Z P,>Gr>J[ LP:Q4nP:Asp7b;MbǛEhʛyWwU&dX^HG`Ztl!3ިSa;`Ia5DdD3|hiuHXa7ʸ)C UFB!g|3'[;=H&OzkQJY -nxnG+msM7^uEqt\oY.?'{OA9G.11dpϗy9S0{\θ&f> stream x[[\~_EI*0 k0^?$1~pC0Yf{ 4R7r|1-nҧR]TJʉDJh57L*xm%){woP;Q04~xA%1c|AfA@1xtVN Ƅ:DoJ{M+4[I aIIAKRnNI)Ƹ$-h^*:ѩJ1.;$=bT3wP&:$ i5z*[x-@^KSD6T[7s`wI´A45 Y*TSk4@]Ǹ1S'qVXDٍb6ԫ:XRwbY^tPoq`2=R5ƅ~El*nZ^2US)cx([XGX̠50t94oXC:D+ގx:zc=<I<ǻ?`t~}}zk޽zN1tWOw?`wû~IgSWc|DR81|!@E0VfkmNlu^'>瓞Oz>ONdDю`vhuxudutulu髳(ӢL2-ʴ(ӢL2-ʴ(ӢL2/ʼ(̋2/ʼ(̋2/ʼ(ˢ,,ʲ(ˢ,,ʲ(ˢ,.ʺ(뢬.ʺ(뢬>P~! Ic5GLVgʸn"jhp0cq!I?\ hG!YP@9"Itrm5=ۢȁk ٱ*ܻCX)ajDES#*b0 wv Mj9߈H"Y5*M"9!7fz  tX(Op;n<4C#rxZz6+y;@`6mfQ PR@Dc"@ ț#u`+= ِE{!XӬ@YRDm[88̤g*4DFW$tV.dY C=M6\!&ؗq3T _BSv:#o.  $ bO{p"^kH/j:RIn^6C@d $=F3ڐԭnO:a$~}B0]u {J1K$ @DY-{f;md9j6(D"}oi)0`u0")D`Pv+ΰȸ~BډPXl: m.L5lL8:=!਷b7D.H;"  NLMz2R 1K%DYzζ nP~ u@& ŏd"H<`R!dQ0#ǀpQ5E!yqJr;|gN\vC Lum=J#~T?gqbHv-A5G͹V4w5aЂj0.U">&Z2T:=M EKEJr_wrC=]( BTVZ( oMSQ:M۱ &IW(0a['u{T]0,Su îG45oD z~a*3SqE:ZcdG:u.J$~4*+ Fq- *ALYJ*Qz˵,{P|Jq^x[4zkbōQ?ëSvWpxo&[sWY  +Rrvp5;FJaS x0B(12f5:fqO Q, SWAy~a d/"(o{W^Y.€Ǩq|uf0$ܸ .೎b|m~ !q$#=npA9VGX3"Q҇ p!c}ܐ~ɵlaO86'B oW).>LQ8 ~kȂi+ļX` 쉅Vt#K_^~^Od~G endstream endobj 1064 0 obj << /Length1 2850 /Length2 15832 /Length3 0 /Length 17271 /Filter /FlateDecode >> stream xڜP[5&www'ٸKN [ ܃}=t 1u̹(ȔUDM Ff^ lokbeDAfb 4@'g+{/jkK6sE.`I'+$`eae2/@h P4vr"Q;x:YYXPҀ9ެcK< 7js+'gvVv4,, Y=lhd?yoĩ!v5u6c0^r\Af@'<) nH=ff./_~A@VAd&fog8#0̬L]&@ +_<X`/}pAot%T%s(DE=..3ᔍ Of\R1s0? __lO;'Wm5O{9.$d֦֞WFɻ'l F7aDݘsIN N2fApRrv2(T}ԏ. K&3bm`qkYw`7ZW1a<-$s$tt $-H`E܅CP`Zi|%[vnE'TGQ->4p 6ح tNd!MDv?6q撪 )''RxYׄuqMuM ]IKMR;2%tB!ȡ5y %>Dwv1Mx(FWJ? __ [N+6ㄤ#,O`'zP2Qcю3IV%#_P_aLhdQ9ʼn:W45^;^)_6~PC U?R P=>(N0NAAP~7B|>Vn<>AD >N091ݢTt6qRzCE^@[⑯,n~X(Mʟp봥KJC._yL? ;:)]#.A#?)@w;d5m hhM8=@*Vv.[8Yz7 3m\p_G4F&͘W-O/k6=Sۡ[sy*;@ o6GD''lhϖijߪ~웙٣KBT@{xl&Y r\lQBt3LuY, ?)_{OH>{?FQ{~EH;QiAsup,edFyD{gԨ-ßjXWx^/ks\EX̣J/6iN#aXQFgo8%Y(;xZ} &e x7Y{U^@ϹE`sOIv?el˽B?y[]u(K]L-i2['vAym_g*榅j[:,=5]k4`c4 Q>'V~08Ao]-t+V4X~JMc.HWd4X5an6#ʵ~sȹ +t `8y)+v~;+ aB?M9Tu~&l7ݕ^O q ?H\k@I52xd( i VkluRBN<_s#m B?nfQ ^!. uǣ- G^6&LUCA)ʀ&Ռ9#mȿ .I*HzL{HlXm™OvTꑒlx&HH)[mkջn1 !</%_'HbI^/SaJj(RtV*M |ΐM0Fp?~XV'UopDs-Rnga)KbigE)*ȷ=1ڹKPjҮ`(bV2sgK;t><9Oy" |ŏihzrƒγ)|ғȡiMN0(@R(LYVm"f\ߝ;4[7N"MaZko='juoN,4{` ZQVg7o :~& 'n&q/G[;SMaXwW]r>`œe fcqLei[WBVE.񣋤CYx6|'iYmEfo!Dݸ2CnEDbƨX| @?>R[nxzxee.>_-SL|Z7]ppU!h۟dg7'Bxz?k27"cYՉBUc4H_"ҒkB#r8#]!H2 C(G1.f& $2~6#BSrdOIo"dkLnz2",-o&bYqj܁.֓]ADJ'c{s-0cqC9$#P{*L\=u۸v=)9ke1[TQj@G7L']ʱQG[h VUq7u(vSg##0U7]rB>p8~V* cxڥ)-j^VO%` ]Bq(%0#7Wvߚ贇$I0]74=s7#3iP^ϴ0iКg[˞-eh,^6Y}]s+~+fxsQb8na/aр6\!#@Gb 4MN-C)Ă+?t&%'MݛBi$8~Wץ]cJ9Nħ/I_> @,ӯM*0(6 0Nw31$=]LTML-w5* =z—~Ck&)+NMWDJ*;)>Y ieeg0Ջ f6/êWI?4֩AO T*m*$nRuNzٸ:A'pv14#N0GpΎ dg[vџ]e?6) ɲ"N&N0;!ml{j9d&w|P Bf0Og'f=e]$!f{% B0r s-c a\yOw;6M7TgqH(O?[„U+!pw7"50 j{ 1eB?3lK,la`ڽe+aLJjOrax-[HfwC-B17|MHUb S} IͭeboF< _\F>YkpNbD]qZ`<ğDT&V4z)I9ijoY}0b<񮿮)iJFC`pszvqdrIC4 d`wе/dsVՍ,sFp-ڿՎ 25wS $ '_Uؐ,x6ڽӯCs;_qfZ'x`'8Q *֬$ PӐG"+v1 .٨يw+ZuhSA CQހP$\Џ(f=]qM-z" /Rc)EZ Ler1tMz$xadD,'^`SĊ"ov-qh4wkqaU"U\e_CaE4loJ̯DVrJ^>pBV70W]?AdyHK],B֞(͸Sf4hQ:SS#2]нa;uaix[Bu{kI8y5 nL@cęsKW Za_k4qQn}G -aP"&"XӇ)QR[iAn[uKMQ L2;Ei>Gm):;oĩ69ův`:v4C lO,>ڷ*Ӈb[߻_ e~G1?M_yӢe++}XuGI|S^`:ڋ{ewRZ8(^#w|is`̩>K1DVB,yF : ^P\go#߾Pm:E:N!Х*iԭXA(lov5|ĸJ<\Qi_aވ8N?e֜}L;zA -V-DQ1Ϻp=2q7_sukx9.,;?EKY7P'<^Ȭ));&$ @^l227V 0Fwņw6̃0b?UcdR8| w6-M*,#=q1_ lhpm>^qS|(_P7hm:GɹYDۋIS V" ܦ4OUz:t=V WEDKgcUWg~ j nj>1r;OIf8μRomIws7vm\#sZV7 Du9gߏ0hgH I(_~,PZTPH@W"ST+ !w˃OFw(pv<gҕ$r'̂ӄQoIJ尬 F}CqbcY|RmIZ4vˆa.#| ,Z$-5DNz>6d~jT()RRA zsyM u&9PuDb]JGs_|EDyv# ȤԀ.U"xEe햟2/s^!fȿ7|xIdP(TK֪ sG&"Hfd~@G[s*aZ(yeQ\+s"XxZ׆g!h#e adW)z|ST=*srv XaX4܀ֲ-݅6OWORskRϠ$ *KĮckX8i3f_ n=5MRwjD+޳ 픻.#]\,Cjt7r'b3Q2FL7?a6}J6Gϕ_”`M!=| @ 0cۮYnsl+[ίXҹ~'',Ѽp1~>up6^^෽x99GP1fd?8sIbYժd_igy~nқC*o Y 8P"=-tNyАQ>2~'oN^crH .:`] F ƳBi)&\ /{fbF$KmXC;t6נ yĦE0.LhX {99t̍Fa۳R2q`ƒ5;O)l%1APk%R6]l#$8$a|i5qUa6 t4o#{qs V1dO$i{^&8ŜϾGM" -3y-g Q[ڴd j;9/Ts@`|:L!ϸ#gch϶ 4P]1,_F*6nɒGpJ2v qFkYX7h؀*vJ٧9-_^4' +hVb9."ݗv';ͱn5.E1okW~'2O$w2Y"mxơ :OtJ `urG?9-_wZ6dzWeu(g+XsOEҙ5Boi>B2D)Ctn_3$*^i ZK3Z{ym]sbDnUsHyh]6c3g,D oyUNwŶz>vm}x YEq?:g|8~H= PI~BQ숞׷EB B7<2H<@׼AZ4|(M~<Mg&A99rGގjSm\>MB̬`|E4Eg3uYdTfHy9 y7e33  GfaC?8X̒u?aGnGqj!p@ם")w:^ўfBP.wi*q. ,t 9g}Ж-O}d f?act DyEAc:uDp`AY>Y J". T`3]lh;wJF5۔.*C!m|p 27GSfrsܫ?v^FT-ot1* lbB}Y0.-Q=6>w<+6$5"qp$Y.Cs y&*0J<͈05IW虤OHXuw7ݨj#a[+42Qe^Тխp`A0tbRsc] F7W/ۻbǿDeH>ҹyrȵN^ *U4(aPC7s{_ Z|}EDL|Cۻ erF[c I͋Ѭl>T \k8,z51 d-4aGd2#i-1g c=邕grW2Kr d;\H }d(9Nv!ʱ1@Dx).~ Q$J{\FҼ!(iXWI 4 j XU; 4TMuq4oؾ܏hy+sȠdr\Vء߳1b0*X^iN|<6 3!T f[v25SzDaȋ;(ܬo~'6@'*\EmHά+B]`%!mE3bC C煜̟ jz&7(İfvv+,4<_MY_ (d\4TB^U>9H_"Q` /\@*=c>*OsF%WJyY霠T&=qϘ]ч{Ih|2d?عH>W'cu pf:K.)F㞟n|M#WƝK g U6q]eq>ZPLc3<,*6k/-CQ-hVv^.Gx4ƎM~sSD9pt&ͰR[+#q$>%t4YLDOG Lb7[{,uKRcVya;W >NZZݴ|12$4D}h>@G) lV#T+N@N*pPyU,E eKԮ+x6ZWKeFkPpTdb3~|#a;oh\Z"Dz{=ɷ[w}K pOYfzL" HsY4SILЁaq^amH"y)A@i~-dFk:0Ϸy%%/ |0UM(6oNe zKbSᦹ%])z?@T"kI#.WZж"6QYJ*[>Je0{짼N)]%%`, mExiD'z wsV 7?Ic;o!1*oUF-SQlyK<ySڂzjP4#,M+*xV^Fm%KC?Iɯ9h?WO"bk. Wd˛L`#圥&Y' qIWS-Bś1qj@#B\S'XP> OkjHM;\Yobs=ͦsUz/!JZr6mZƵڸvxoockWOȗPV̚S;j^J ZW,_HWUU@/wCCʔM7Ho1libd)]̘(h=EQ8[&baTl_U S ߡ}QSߣ k RAD}+)I{7zKi?듮0n?Be;Yl|YxgdWv#gʮmpIyĖVw6gJmZ%y˯sz$Tcap룟T]@FO۷s&j߱LbAxTQXјZ{pQXB,TVԌIa Vf dUEZ2Ȩs~ Ŭ/ϖb.{;uȪ/ RZޞ~*᷹7MA!8-Odm3ۘ ehPeor{ȿP⁡^!c%iNOc\=Ct9[!dxP~ 0Yymtf}ldb2Wk`4`9C5WNunU+}8|ɝ7Y%9S 0IfmcQøobVz򠯉ߞ`0bvY&Z4GnvHJũΔe펡tqAG/ xy:43Չ<q3} DXDt) JKrbl3ޟIKƈ* gR 7BU;uYj ªqK;ELF6r i%0IQ+.Vˋ{SZ{l*lIB\lXr\cZ9WV/ˆTg\˺&@'1Fԯڿo!3-3|Þ FAff >xTtY.)/7=pzU1TaٴШ'uNL~Vl:x4tմ==1T $94|Sj|S;L,$,϶*)*(TJgLqH:eXX}NGlP*F0m'(?Ӫe!lu*\ߑZnԼ~I#-*PF0Z42*0 (fS Nzz['A{R'Y2Ky39WJ.Wޖyس5ҚĔQiMօJq2? (Bm_[ zBD*RC@B㢤Vq9tOxA#C,+CT d}_۔7vp:K~<z>!#T/ֽLK%Y H.%P0v: rG~+9yX%7`d#Sq̠}6bX9/eA;,$<0F8!) .sX7JEEۑ9Z mvV 藕pHM+M|FWz掙F:V~(tDj^*%\ LDCfSV FP*/Y,ǐC:>^z:[${i G}gQ_ n|5^Ӛ]Hђe띨wP\czz|%-W:` 4HoziDGq}r⽻ehw v%~sl*f oп]m7/=~EƳynI/*\)Q`3Kj9]aicN/ào.'XK|W4bQUںQ.1e(h{U蘘KR !G,?9I)%T־~G ɤbG~xME0YvZbˢF5+ATX: 8-Rif + Vk8iJwEPr-b]K1ˏ4Mr"22g^g^,B$qDkxY̼%}<- (:u& qa^L'[v'Y5n [DV`2Jl˜ybUˑNWl :)xL&^\g?i3 Xuhv5BZX}HBUqAuL̖ϵ8I p h 66ד7(2I:i3_,{ǧ ;$,‰ܖ#Qa j4+2uE2 #`F_y !6I J@E2P-^斤9  Ϻ so"- MM4{۳rdzp%emTmCpg bQ\~1u"704T1SZ89HG1KUUrJ$w i j@yW cQT3 ?T>?SK>pE@#󷨧,!~4l8myoֱF2+nQp9#$i ^|MUuq$t/&~>J3CmS92I| ‚ a1beVJ"==ۄ^;Oު2Xu׬˜.p)(nfǝmLkub7ٲ(պs7рwq5>^R!Կc$GgD 774(?*_%I4/a},E,?Ք+ {|电ZK鷾^Cޖ͕[dFgA:O11'SҖ Bh53VId #T R,F^=Cq=m$aOsz>&&C\UE A4*Tx=oH9Smmmo@hl磆?405_RJO p<'AsdCAtS7[JI<#t?.j0G%Zi|KXWM@89qxޭhG-9mLq;~fb#A|!^ QX7χFGQ8Tbt_H"[o=]p?)cO|*LمD/M`*ٵ|B? 6I#n|3qQyb~p,釁rp)P#zqT@1MJThgIŠj#tVl@%L jbD Qˑ5`<ɐ嘞)]vS5LEړuβOOƮ,'j'mSfmwH>Lg8n? qglQb4:a5lǓU?IY0AO%p9Tɶu kal(_KFe-Mum\lag×Fs=2!{P=O_2zʧDŽs^J 6ª+14$;Ξ)8JI`^(yǎ/NB endstream endobj 1066 0 obj << /Length1 1144 /Length2 1528 /Length3 0 /Length 2250 /Filter /FlateDecode >> stream xuSyQa"AXHx\dDg"B+1+|&WY#]AĆ#t rt&TA>Z4s:¢gBvP#X4L,SB ]3i̜!>@͝[q?,fδ6Ptw'alPXp+c62@gH4Lx`Ѹp;џb B;E`B !@5|SGa5 V ku^(o>H0fn_T06x)"o1WB;Blľ  îWALd3Ep?5wO-47˝dq\xӽsiiWsYw! 10uL 2)5,fμ87 `px.1"`P @7C0sN0aB0 Q̯4xf.=eςAp+P/AIg'ϐc0nYXm,Zn+t^fD6r)m`9o9L{c" j湥i0=gCT~Ф5EkcϝWFWO;T&#񺓛Qz|%1͏(u#%[҅S.x^Ѡ[ꨂJvU}E*&6޼d(۴dzt̬]ӣ뫻5S^ّX}Dkm60dx0t~zli^Kɚv󶞆{k'֩#%ILf=?x$6wjVurhu(237k<]iu4Mтָ'" ^&?S^PZo#fn=q-ޞ'IS 6Ɖg'v5+:+E-%F#/7삯O$1w_H\W8PAݓҨ@BT9>2hZJ?U7[qf*L&\꺪#oXl-Aih\Fѹw)}ʭDءx5{b 2+: M%w:~uxe[ؤ=j*/ާ z:V]q[e"Y)sa@&YDtd[~Lwp[:eMY1uX|ƹڪ~9qluL,a$+o[{$mr>[4|x~p7>Qi\XZT< 0\8e@<2}llDUޭ\Q=D-)p#1ve9k|U\3)J)}AؾގWuЉ<گ4kli3[}!FW7=81&A[%E R9etI犓%?Hd)g֍{}:drވ>~s@ҞhReQ? {#nq69WxKKԇn7r겜p=*VmI.xu$ #c|?M>ՙe:Y`{Yt2C eͺiۍ{6i8U捞5 K֭^]%+ ڍ#VE\~E"Pk~%lLs+ęyoj UVHF`iͶ8QO 6kKZ$M sSC] ąhv~B1Ja:`:>LcKRa-4&w([nR(UK}5*a㧬'R4>o R:`4V̷(2語rnxjo \s͓T҅ اPPhy`#qRãvEjA fR[SiNuC%eNy՝թsG9޷h{cdE>!Gm,)hi|-M7Q21dՈDZêhEm 쩒\h endstream endobj 1068 0 obj << /Length1 1626 /Length2 12329 /Length3 0 /Length 13174 /Filter /FlateDecode >> stream xڭueTۖ-ݥpw'݂ w -$w9ow^2\kRMU (fd؛j8ګ8 **-lov^dZZI)A -R@sSPP bce 0hk023W?=o6Vw ~_'j5`i$UUd *YPs3٘ĺ@F \ް]W'[tqu}ظ\Lo3;lAnx[:M- v5wqުI'WmW77-y60W-3 dV on6VdpZXo0oM}[NN /6`W Ȓ 뭦92_"`?}@00pty,*ිl> ['j.}Wh7HmS;Pи_)6 WҿFVd>yHd`Uhf6X]qHӲ1sKJMɳ˩JK1߁joKrz(;Z/ GO+'J ϳ)`7'v0wkm4o_n..o}ߺ;z͑߇ڦgekItq9iV9vGn gJp|w5[]qinj0롰kC w^w0ƿ}oJL{\יtwvsWXdKhhT\c beeۚWNl+a[$V}0Co .!Q;. 1w<^]QeՅuJWɓ0-ś2Q̟WYn ZH"\N/{fN8uJe窨Qwgq*-;T¢GlRZA^1t\2>m׶m`N!7z][/存At6' pEal?&ȊlP_5"LMI WxwH8R#WďL,;THB׾Wr+U >Wm@6mKd4t7gcV$d Ex$_]wìWceP='B+Q4RfXFѥF `{7ٶ67X}xc \E ?We6.MĂ!> zs/2MZ[ÊJT3xq9JlKztTE{&#;yT/7Wzsqp0ݳ\Ɋ6PL!lo%A %}Y};@g4))QnŲQ.* NL\I?/֓tG&&X*Pj&L\Hs0u4}w||)5#ܓyhU,zhmӸi#%^2 p (Q+>)dě"7>9%̢8M҂S3viG6K5<ŝ8.-U6O_Wd mѸGKUs3lYj}ya =mQB񂅥G o[b"m]~3Ҵ),Բ2R McTg ,a.5sdJ||L$BUY M6ߣSw^ΫP axٕsQ;T"I`SMzh#XN3+b5+O< 2>O-Unv %YPxZ%K|ku nf3LKO h|ЍhdgkskW Sʝ7mu9KѾCSUm& T.ifNWz\#HG`8ۄ> E}{'>=bݕH5H))*B;OaINIϖwߘ^3qwF D>}L|ݗK(of:>7Z%ŢaXuOY`" P끝 ֯UNձwYiL{w:Bp s\x58 4"" a/`P?JD3֩Hi|a.:*^= ~oBWETnSRdz|3+`Ĥp(I8k 蚲={j&1ҞLXܙ3C'0W6#PއHc'a 0#1;>^98iS"Q!p "0 %W񳬗#}E m#ȏ@1_3I<hSr}UvUk#)Jgem,}}]Cr4\$#;֝V, oyӲo)H&I0u&чafq=, MjCߔ[̱8/PK/ D)kA*G,/O:w.%,U&4NgH!VŬZ+.BwR#g 1rɁ4]SiwS8LABsF*u!*,8߀ѥ/;iBkΓKG'O΍"#$gQNYK烄_JVuk \ c܏Ec\mԹ(iT%͓"luFٰé+Dc iaH})6Kn滏ȀZ'+[G7>djIv:/x^_ȪO߻:؅j/$4Lƭ`I8VlIމ_V 6'I_8QOa(~J9˼~WÅVM>$HdrCSxKĚX}*T!vS%I͜IT)(=C tC-1v|(,W.rF>,a $S]j(ȖmohsˇJaOB^G\+7zu"Dnpj͈I/ 0FG;_y--~/DʍS75 upQ=Z]!kP7ϯEnʬp 8{ G"ΖPc#ԠϋN*7zÑ7;Ge/QEp8F.ΐՠnqUhȂ|ʄ;ǣʙE%8 "dֈG#QF4^݋-KduN:L&j{}2ml; 6&#K[v⎨>l U}fcxK5BFdW.*EĂ(t*Uk'B( =h9wVB" (HqC'v٣8,y LOGpIG'_wxeI\FAcs5#fP nOm@vGol)^T%ƃy=T{x(銖-Y'3pkYR'>O"ٞ8|@dUٜ>Յ٘k>Ɠ, [{JpM %,/U. gLy@N=i&rQZ,p *?EZWv0Ɲ2nGZBH~n1rHNB{=Hd+ß"a}QABv&#kY vd:8 Ӌc#/zsʓ+ek0*h *P6t#$fҚ^iWw|&4N-M%]lF]߼#Y]l\}F[%JGghxy4 _+>Ta/ѻŰuYwY09#|vSc2 )vQ]z;+a/ Kw7i/>C'bM$ 0_W9&#vW쁚|Ѓ1nh{`մ>3!?U]}.akE?ǡu4U NP`Rj_X3ܤU6hY-P'!F&NOk҇Tb&OuX-aQuʠNأk0:YVĬ,O97l4|K5 X{?ancwCaI:~J4XYJHUZ$u_-3E ; uS9qnIGQW_$ 0uҖ1V(s6oO #g86΀^J0Cb)I#>/NgJu,R8J_9M =78R|桚Y p  K : /iW"U9rObp˞Pubb>4,BDžX#mLyz0AꞋ8b)ډA! ʕB7 -R(PCvVJR+*`IY{TW>r_ G!=G,gPaP+ߙ6bhݏN̛Z ~#{6 / d<|2Æ y)zwV P Z@ƏWܜSdH@2;+A :h1/㜆#*Y `;/S'wdCa*ZncXҌeu˖FMqK&vgT@k?8Ҿz-=~'n&c%1LP"(#~UG;۪mK2]~Z3,O"D~;G$ +y}lbr4 Z%h/D(Uy=C`yS4QXIQWy-whhRD(&2n񠨐7;, rdOLXYʜF`hw"ך(7t呣7ڔkk#wqEgO ֳV vEbsb۠y5!fG˖v=}Qm ߍ{ᚐS'ͱ_t`«{gګ ,xHΝXbx, ShBE ^(HKˇ|ťܮJ{ lkϔpb3[ c;k+  (*Ąab"S9b2bެ T>{3,gO X G.7XMPqڥH>{*C2ߋ 68Z-t$ g%ћ|)l?}G}!L"iO(Ҕk[CBcXkqԘMpl9>W¸"[)xdrII]lcw][b"?N ͫs B?Ig*bҮ&o(%4g9XJx!^>:!Uvw%sP\Eˊ|])DH*sq',}FYO59$r}QnI#eL&NRN](8V\x1;BWT^te'praARݦY }mOF$pN Ku)sQmg1`79KCh؊)1Ieh-]VM~+׋}kl"Bt{Ҟ3{!|ڢy ǚ~ftɲ fYzmqQMpn^PgԺ Q|.!&EW GՉ)VK9>r0I!*)M㾝=ǥG}X;H+T'pi*mo"~%#6r]-!_o!K\BO\2I{|-KDßkg%ڟ^1{%!* MM{yfmv?uc`~Cvn85clX>eK972GG",;p&[ 5߾8}qA٫5BGYLww%0&fg9o9-[Q=oÍ%)^v<ޟd馒\wz٭ IZ]Uz$V(.Jgf'ha>g]$ 7u ] }npŧ~h3p)/꿢+ %7pg<]݊afI>͠  DD5s-8&)%T9PZGԢ1NW%%~9Қ?_u馒.Yyh"%N};{4}ܨ[F{a04H{Ɲ@H_#R?@z4Prt$@qaC(JJ~p,]VTEqcjŹ:6`_填~X\Ͽz{.nz$U(%A-W|{0 3]pp:vw FcM0Za\7e&}gr~@-(gw71 ]D ;Vەa84a&TՕp"R)=3L+[ ذQc̳蝯KVK9x23(_Ȟhߪm9=sѥs+WUވwJ_EafSޤi4v ~/~ ^ud[IFŪس z#4%tx9AoĄHP0["Rj;( +!|NT};{F=8 ݙ= $O˕ZIgJ -]mS,GzBϧ731LP(k7uJ8,0^ZU\«V{W"֘CiYC~WPh~]ؖ^#e}9Muԓ`ʚI]V Sw_o!Usl2rVi7ۨBVfP({3 xpUؚ]9++FUvWl.(:2)%!jB& ?xw3lmFfGJMS궧>'5 =hit JMUN hVm3K%xeQ̉UE j)AIWe論-|jEG4zԾCejnzٸ=|dT`4g)?@_ PWm".eBzLH6ͿwlJW\+i(r$駗/9rGIwO)Ymjbڟ/δ0K*` }_+;@?"!LJ@!d;VL&^r~_gnEg')tnY\<+uK.4a2͒xt- &ld9S| $št*%?}I6(k!(r*-Nr5'cx,XN601K_n67q9sDoe=6\Je/j| &ʩ[C~?&C> hDE e݁.+Xw05({A`Ȅ͡Z*TRdjGZ) Nf6OZ!}[Ej?C/W\3Okq3HnbUs`aF۞'[@:k&Sȭp%2**"OZPdɩ̄]>/%[^4‹b;o6CNRŬ,K.3Z7;gnN~+dQOv\^jQ/$,yt *{t)I> voߡ5?']YTQrRV飞+ 2@Sp9]IץGqū[O_j9mJ!wE9%E#[qH(:7drų\ SW-Ys%14ؐO"l_=Ot ]Cϳ'_u$]qfݩQoG:e_+wn@<=(Ynql1BU*ckpEbne杍iݖNTf"H'S!y:r pZy(4`1y@܇zvˈ U( s3;pX0EJ6%#tSRxchZR"k&f]n_ZpOB틴p?[-!R* ~+1H"}=(U4B|/xta7Yf׶DY?C(x/~93VʀDC9]2/oOܕ&(kZFs'9E9M }J6RtP T憼EGr.O9)Ww\фkv=.e~qK bp՘!>"Av0$0q]게y!mɫ5dKPLiѐX۶X#Y+ l(¤ym'ܑJz1Zm+~q pr; G(+bAji > rpk,Y\2NXJOzb}˰{^.ՓG>n8v/#\%z",B >F_2]Cs&^%wF4qs zjg9+y$F)[kze+单O"W_9bnfv:ڍ~faIy zŀ0x' )6@ NJ=v:V+j_ٰq?FU7Hnxƪli ΫkJ4pwhII|kҙg{8z_6h endstream endobj 1070 0 obj << /Length1 1630 /Length2 19467 /Length3 0 /Length 20319 /Filter /FlateDecode >> stream xڬcp}&ul;;mFtcضmuܱmy̙:̗aWkVզ$UQg57J932,mM\myՀ.zNxJJqN$66+///<%@di@MKO\&adia w9T߁sK @\YEWVI@# A6KS) H 0l-L,i͉/4t7:cb8ANNN 8,Lm\)_9zSwrv2Y:8fUwߍd 7ifoOKku6s8ݝeY:9{d2\,,ha 2:9tOҽǿ?ktvژ3i7(d(#+u}e$*9mQmCJ kP\0YUETMw:(Od_~[e見@C*@T?+3V>K]pر;ڹ[bslGzĬYZ |n/I U~!U}Kв/qzSJ+<ޓj Ä]hklJ 7lq|)!^#_x2y_8ؓg^FZ 1aMf鞇I Lh4r1}J}'H&sOa{J6MVn.w7ӌFIpZ'v,ֵu V 3x356 >+܀iZ.;0VW*ULm MQ3;l:ĽWW[GnKf"zG)3/crF9lz }Lr{ɘ I-y+,O7*kV_2L 6{rc- D-;Oֻ[kMP%pV jHAuB?27s3~S>Qv*/c)+J\/tALȝGQF|:hJ J]B3`,ڙx]ŊfOU_t얉WKu=w;o6l zԳ7&y&L Fŝ.D?xl{ _[?dBi#WjhO,iܷ;Z{M9g`=)P9P+漺D$7$D, ې`w/:shKvF%6x-_|:+$u5Y#SLڪ&,`){pvTv*[VQVp-:IiS-͏H ^pD͟bg:rC֎IS(-K7l:|ECepk6Zl t/;Uh\lU.~nw42pp_Zr7(t-#cMv3Ρc|{ú|,G\~18Hɣ]O3`LʓJ2vShC;Ód\wL0 0b|E\!E1Xݳ5[SN Q _ iBBCm6ߧR<&slq|1`ZO[Cv1G9~`O87iI.Î5jK­O;{wy( qD=KN+ N 4w);H/itzCYm@lkmFԹ3ЮDQ{ S* d0JdO69ޏ$=6Cc-L3Bƫ?RzVjYO=JR%fK; <(n@^D 6@p` B_+:_bYG[oEB0AՆD IA]qI &*l hz5;+.c sf bo@;9Ϣ}_:X2u!q*fѐvy4t G[=jNzZ$, ̛RE,FY6ڶO,p_#:ɜOw韝wIbc OTy,PK.+T!U^?Ϭ:Hg!׮"TI7EcGhnVe SUW%$1D9"h%L殠%H{Kx8|xJAvwKqǰCKzLHĉ2e0c\1vDSHm)GSl@\G2O,:|G@Wvo*? έ!G>W%eZ[be@"`퟈ Y_jyHߦ-K ϫ' PCSΠ3 `%0Xkb4 np=_=c0F4mHf60Fc "}FlJgx˝) Mf5w A1x8A L#ZCl6k֠՞wC%\3M}GsIYX'ĽgoxL[OԂjĒBQ߈񠑭,wG(~T!lؘ2LԔL6dF>d~K3k>鱭|rߵ4, -HRŴ D(/OA-|C};Ϭ[9.dxi?xƾLnpcZ k1 .\%ao)FI)X迴nҫ<~[e %S3@FV^bmas̭KJy#͡#yLz>8N]wk+ Gan J/RV 44,X<ŒG!xCH?Ŗ!MjшSi!Ű<ޠ޻ޯK7Ԣ)DRĚľ*/LrRU9̶U2XҼ!gmOf:rϒ9J? 6El6[QdKF19~jQS@~dFпOslo=\qpɥ @Кàdg;wj35~!y>TR&1|B4%yb{c" -2Jܮ?K`+6t?y]s26őWuA%6 h$i}0t NCy%%V܃e Nl/y?&#~*}-i's dOx=q3 z4$nBKh*>3<1'b(ߨӖB)gKla.[hnM glu0av{8~U_'hq52vޅRs3;LPug|-j;+h?q|rH HbX++z.xtt@k^z^ϯҀ D;wgNJ2qlPҴg~!!e;+GhԺɪ'*ξоtZ+`nS끔i7޴pw%@.d|2e$[:+Q̏ ^~JT:| v_Tнev^_~ȵc|ttN*s73&$;ؼ俏S-ftkODl(ɔsd"O*yޑg,<-luB d/:*y8ۚAy2s0jfCdpX´t9^~S |iR5sh"V@ގILM j.NaweE݌6}p^]q{rbM6pȚfբ!z(G%>emպKX5e+a dt˻"5Bj } Ň;^/sσ{W#JM [>ۂZQ>:| #NXݯWCp]V ^)&SGr\-=?In8}8*ob G9v{I65wkr#] 0u[=_C9d3lK@ ْXrDV(f*.^JvaAzBᜬq.Ywt%)msEً<鄲s00"!Q]=;sv&K<]pPv:ɫO濉PW3}:Md߄0(ꍂ"q%QT3 $N`C4Is ӋUAN74dpߎ}1B DFO05knƲџ)-ar(pQèf5w|d-E}WA7"/zCD;1ɮӟЫ5i [r;SBq[Z;i5!D\DA]"tռ/E&bo?3Llyu/r3X3,bo$e(VmLdY*&;kCkwGa`^(6rg̈n?&ϬLj*l)]ǫuQ 4SIJ:#UTuѣ]mF?RhSJ83ŀli^=`^^9y=EgM(NbI.Ovl͈d dQັ[f!D@Uo]r#OvMRCQ~u C $U6 !shW}_RP1M&R*l̐ב@9d64Q oEwHHwczpwf۔O:H**Kv!Ђ:xM-Jغ 6~aBX`SIH%_Y,ˤY y(X#5 h[qέV. >wy#v9*^KnH>'j+2V}1s?C&*q'm صJ%{1;lW%Z`4P+[4q$K,V|k`>*_ꨳAg{?ا0p}ʡ #vRh_\&y+=IN<%X@놳֢E $=(3{ -F}KG*Rgr-TSo7+]b[C799E}? r0DꞆ=Jl.稬YI{&`Cx_0Mu]%pWPWtI{p) }r͚5wQ>?OAM)Ҷvqi;u/N؄MtcUE|.xnN*K[9"dIBy@, Pr,,E[H]P"h{w9o%t" f~k!E 4ȩMI~d8 )8M\gϏbeNUt$IgEx&maiBW^Jv;f|L^Uԅ(t-bԭI`C(pw-Rd G2했TL!xKʭFDY**Ҟ tXQ_)*n)V[eycp.bl{q}aOP4S8+*|:`ٽ+.N><1 wvk 1` ;5/^hZ^*n4RDA W$rk11,d,w[-I~IN|^rwR%`5Od QH,U IBuKPpRz7%9r= [m ):U%eW ru!s'9ŧLK+֪uk(ڊ"M".VfLV0G^AfX$!زdmT๮c{e}wQxvrI{ 4]2Ƙr&gX~=04TPMAkǸL٤31z\ x~"8ط)P Xfxp-/iԆ18 YxuG%VǙK32YOVa1Gtl(^M M>P*y\ewR(]r"8A:)/Pym u.QjW~@ |DwЂG}i hE m E ;۴-#GmÝԲ2tWN[/pgBc~֬u GLI+o3ߎhQg*"F Y '1l;thF#V'pOd<7savve~ɖX8mKTS2ad) I)=ġ7:4T,QH0uBfg{!wA*Qc;6GT 8 |Q-DPn.9aźJWȉfݜLU&gPZj ^Ȧ/cz@f b MKd |` Uۣ,ֺtJt$k8 N5[?߽t5TO>d:y-jZCy:FA.,0c(U~Bjq*ou+ˬَxq7൜mQ[n^0/k4\ѝv 6I_|_'?Ϟ9Eph2 z}~ܠ*dh88;@U9vbt?Z75l# CA=Z:]OOh~3W4薯o mN>qUꀲ4)'KKfZ t.]# D4B䤀m:m^wX9 0ȝ#$h gbvp>r#~\X!Ÿr2{e3jەiX%[Еmd+s#veKFA 7ȏȍǕMaݙBǑ=X,hBoHքT,Z9-UmaI ?r|CY|>xc+ERKv DB bJ-ahvYzX E&9Mܰ?+=ScmR8{$$|=kDŤMMd2KF=;$Y޷})z$#.XqUW8? ,->I*o,? Q%u'AϿ-%?m -#:c&t9Ž\O Lm9(fl2oO`Y\Ayܺ:$ z~*թؼv@{|Eb5 )&aw5^%]Bubw5Gh%bcWTwgoxo:^+:uJ"۰y/2KV$2ܡT5yBGI~Rs'ޮ󶕄+Ma~pº6.yA$+;|:9 t,>Fmw`rՔaX!43~ؕ:ijn\o&찡4RpK"ݸC~z}XjֲuAs('`nu#8LJ4n$!HpS3Vt>8c6vwN;Q~O"9#T\%ʓceQm]EG3f}o&Ahʐ+FXJB^Ɣز"kr:xu#gOԫix' vHr>lw >xNn#zH>rٸڰP31 er+=9'TWZ9\K_ d,U0&#Pd %V7R}zÃT?PGjIbۍdLRVҾl2ۀp^(:A&̗0] Г 4#y^B9_o#0sy_2 3K}?6k3.>[:nǨH VJ&};@U6W16WpĘ7xΪXc5xNsR8yYCef:`߈p0U-&h"s ;ChQ5K0Cu*zi7b0b\1{ yyQ\s2!Z֚kj) Vi"iYۇIácX R׼ԇ1X Θ@&_SP*X5Z:F2^tӃ-`ZqHx0?5ϪBwwwsD+_^E wz54V|da8qQezY]ѻؑE(JWhVe!*:2J#Xt5ͭCR'{`o&,M.l#_QC5r TP-8qѽWjF, L%DtbG*4L:UIgQ Sȡ:fAu|9\#HZ0Ԍ̌ }s$1aB s_M>5nG#GyGir Dao3}]/5kCx7Ci5̓#P|mtZ0Ydx>!|fmL x8*9nIu& P嘥 _g,; ͖̥e!!$TШ?4((^q| qAfaB n,ܱsZQqpWp6-;z-}UCAG ö&[- ][.pFSZRpE@hdsG;a5wwDFRzBc` 1 \í4f.-]COG)v-%3nx,k7զ YH cLBNqih-F w҇o-Q{s.'BN Ӫ cpjQLeAs9 ͇v!nwu$[&nKAN'xt)`  I܏Ryci8Ʀ5]7xzEyKN F [_=S~܇UL/'NՀdz:iD!>nA1J8^ 4C8FӇVź^}A#Ms/韓z~tG6-֨됵 \>>ܛ2eF,sX4)֑$8ha>3.e|5NN'{ֆ*MBy)0%qN6o >l{-.ơXЊ`L :|z*5 '0+L.[l̹b vO$.h.:A yHF(E$K6{ަ-JX=m3ob!7ֺ4WV/qx&/׊M RD9Z}kܑPYuYKg5glgfbd_u:?RHB"=dqKhEXӡ6_IҦ4v}{B94tt JU60kXͥS7Ṅ G(ǁz[%[aw`6;mwE1V`x VAyq2V/fx.G$./3e_sL iR7\ђpkZs@观!y]hβNmTb d]K֐{OhQ֩_ߔdcUc7H"M"=w9'${G 蒩m@gp8WD11: }^KvН[!(`:KmiS90XicmDsy"޻ZhڴFa5i?7gGr }}9elpj2m"O(6 K͸T^F.8vH~i/*Fՠ7zj7lfX훶}(c%8}쏒PJ+J⟤cUdX\6̋lS? ))RUQQg]|H7˥Xv1=kA)p2IE;̜}?D'KNLX#oJ{^jż LmNXd*wDGT<67<PH=B.ˤ\Aqt)֑b0,/Tk:V{K :B 72>P/F53ՆK@Pm,@zW*}iGOZLVVt$QT]|I>ٌb&"ME2O+`098 t6V T|Њq1&}2]m@b+E tZE94ux4MU$ ˠrO=Mtq2&[hACCKkBOЗ5ewNnFX @K*Ζjabq#'V #Egb0CV=(jʱ^,L6mdLDŠŏ! +Κ|$K3承u} E`Xuwtjጆ8$,]L54!z=g|OicßZC7l]%oAt5Z%9fT@qgKv~ |}HS.1Caji9PG[+Б& 2e)`q>eპW} d{>tI[L2ƺ$"@spυmf(sSW 4͔ 䗊.( &y3C,Ԡ¿%* GŰ_[$}}B1zؾW3-)ka wN̤4"ޤ;^06:'-S endstream endobj 1072 0 obj << /Length1 1644 /Length2 11275 /Length3 0 /Length 12121 /Filter /FlateDecode >> stream xڭwc|oflc۶Ivlm7ѤqcmM2sޙ2s>^u$ʪ "f&@I{;Ff^"YVўGAh sSP9]@v.@^& 4Xxxx)bN K& IL]M@.olgЇ`oaifot0Zc37 `rv1' Wge@pZ;?`>v/;8xmrqژ3³~4umg;+2v\CtAg# c3{;OI#$e>(o!?rtQ4ǒ1|<1v|mA67+ G[D,>aadf, )\L-6}Gngt?fөYL/߫4D%$%ˠj1l,\rQ 5O T7_@QQ{7 '76~]@]fFffпco0vfIcSWmA?8s @)Ҽ)_UzVKNุn_ `CiZQa@}z&OџƦI޷vϹc]YڽlTy/Mo!e^A)RƉfŬ'?';؜`/h 0L:QP&>S 쾂ŧˍ3K9&Iv4rm4}~vrnլAKRww%&`8c#xa7s? Ee-J'R59Y\>mMZ!7EKqiP Kf&Ik0sf|ֈbD H.1p%VK9QfA2İG:3 k[ҞD< mA5aVLXU\JoV.wC9 MzͶ7Z&&Š[GWGo!&-XAm3298X|?WYڟMMQzES#9۔nBHȿc֡ˎ2Y0h5:tpu "%l!LLˆ@hC$W;DQR8eUc { viNў)shpXroYdb=lZC 6!K,~G|1{e䌄)"bRAUZ7ZcLDi5m&)R;BUjei _4q_SDžX\*rY;9~v|FIJ &|8ӓ!#A$G}}5)'MxFneZz9CcHGk",'y]VE!J"4aƂUb0bǴRdOQ 0"n.Cj,qZ$F^,m,3 fK|;# ѺۋEX2CWB1c f#K4Pk⬕' [I.rIaraɵ瀎y5H&=d"mN­[/wa+1=!En^pbm㽱駾8:7 dSe+j5MOA3x *Wtku{NԘh%ރd_ى^2+r1s=~3:x7(82C [ K6qZUbާ桶3lwcqFo#!xKFM)9~q #\CNMr t2MM IU}S[VrJb5 "| Q k_2P.ykèZdK@LCǴ&MmDi;f6~mOO ïlEuϾ%wq`RƽiYEaYIbH``v8[a9hgډ#jB?rs $,998Y{)LS鰇f#*p-YRTZ.vԢ{2\bMG2dt=8cv"5ϰn.+C0qK*ҵiU/U eM2O_7}R,&y M[,>RSy\S[2x-2qZ;&%r6G#gǬ@c3V@k`MDB )cxO9 K"1&VA n\ŠIت {,ʙcs[KѺN{^,ARxoo?^W#5+}'  Ewn93X9 @'w/m%NuC6W+T3-u8 qĻmau,\`PʬPw2|΄RXC\e\ד6C/ve$^mk#ԻDS#.iExJ*'ACfYAZ 9I J;0k-ؤ]M e;^-Lk?cεܰ@Y[\O ‹{yGSȢۄηZ:'\WWқR|aRݛ|aت6to @K& f(#} z2K^i<=Q92񓉵Dz^0󉯫YH9f +jVRV{'#[%[߭jU]) ,R9$ XXv~]9Q!(2gT $%+n$oZV/{ v:77QWD-P+W[T.)R0҅ v IHݼRM7[l:H51aZJboFOwȇw! =м."B8dT2Z`՟,ӡ]*n9[5{Y'[Ӂs17ݲ׌*̻u-}H.GMdg S5=,a(2|yD#-hO6mER0"ɤy/X|A`:ʞ{pRAv t`&T(Xmyg#׮ڢ{0b%kǷ5$@1gVjFCݢMGt:;q=JÙ#HE"eWꩽl!p~+;>gѣnfo1= o[Q% DOH@9߽:*@t|jMAo9;D m>@pRxxzc#foeH(>V/6VD+vY1`ΓBʁu\}}^Jƅ˚-mƌXTCtFcwr/F"DbjL#*J䫚\U0 *?+a]Xz.V}:s3mH y9R٠.RQ3su4caz|YVZȐhH(7* κ/d(5F U(^mW o|/3౎B"[/KUX<_|#ȧTynZԭhaS bdghr l1͡@NiS9ړb*8kOBRdX66.!AO񓙵a[- V j:Bs.|O/Wb5p>Ԟ$mi)WZ2(t!\#*%;]E$qWaߖ*a|?|҃ q!dIBa|6wV*V-m  O81L~uz7C)V5Nƣn]լ?sR*?IӾj4J+&s=!)AxPℑv\0. .胃MWP 3dUY^7Q2R~\`Vh*x!4iNH7ZB"itql9EŊiCE0ڌ.??m9Lu(Wܛ8ZbW1}ύ|ˇI|<u/ʤGfh@ycFd޾IE*gWuD8nF-POxmѯFÃs_\Q+,sdR1"L~gd&\Av^.#nV^\E( D?9iv Ruǁk*:AA:@/r&9[*;  F+n;MfpV.6,A}FtaX",sWY $XWEðI0]S="0}AF2-gEhAU_Q)~R >&Nā+-$4 g)]]àWX;?cU%M3XZ(% >Q dŸYis~4@?2V*4% [=nX %=9EAEB𤲪|j5afgHND%+AYKt0rtC_pm0tmSeI} hi͞ 8/R$WvT)AmKs~gxQ9|+a)PPe`x9ǿ_r~-+eXzh cZ/dE ^: ǵ+dF!f{ $&s~ ֺBH ؆uUQe"Ljas?Dd;'s7Esf!C咿/ƣCH<CGzq!p˛,=#t+ ,qKx7N[jCٟ#jW&!Mx0%!FJ>м+RL_+ L$zOY[:4Un"vw1IbLOU{wͮ?aC$Jy?C,'~Yle/A0[ < F!bm5"blkk9 0NO̤9t֘pW=BP^Υ'h6-Pkd RK$C3dqK$>MO"_ARdbz{3J6IUi)&>Yz LO`wPin8ہ3 `6e#0\EĭM  //P$Z7 Jp{U0c0gKi{1NU|;կ{ n &rı o,삣N5LB=tOҥ\qU\ʺr÷$$1'4Z9cح>|P. nf^\_7~y ۃ:љIl SUވ> dH }=}aeAK}降>_#O9+ӧc,]Ӵ1vXG+C^pC0! 1X,mӼ//` {1#Wc|n{PL  ޓDi8d_g?朵iؚXN!`4y}/ 8=A0EۅhtI[F# E3 \vv EOF#flBCRurQ+E{("BhA)q0;!pV]vvy~0͑%{JH1!lD^˰!bwѹZ>A |!fWg MS״0@OL+n -li-boޡ&ψJLZ\AYBUthʳ^]1Q[8DQP-XO[ Zº{CA ӓU[VB~q7)D7KqȄVqoݷL6{9A'<#S7pM[F>̛~i9(W'cۈ|: C!CןWC6IO~b5b%dϼvڂ$\kc9[ 4҈?CcuTB-vy+ځ 籮:\ud!a"`<~i~#=[rAaVN0yto-$#q9M%8:*KgTіlԓ/^bă3>: #8H9O_7/*(-=B-pe+tDU|qRdu5M^ Ďj2׽͋JJvտr8 ?nWy (mRVx@t[V+x_)Ȅ,`as &_\1wp@K` ˏq}\unɒrxK˗C[G?ln~B pVVo[=^?Y;2WA`WzK)wOOH < n)vγID0LKý =ܪj)rvuȅ7!}g: ȡőb$SZ;֧E NzwӵY ݭt| S7zc4 7Ft䢬6%wןG 8'ř~s55"6̆O!f"r@MipFP" 0o:' EzrDfrprǗPzγG%@6Fбduc/Gegcd74*n83X Noe | (;Op)D94ZR'-B"^`kU,\O25&IQn7aEodb {nsI0Q>H5oR߁TOl2t@afq%T `HGfUhdp/,W('2zn0),K#igacκBH&pϙ5)KBT$㏥Sn݊ UEbL O:6yVLwP|h7;v b%]^JQ+?EAm[DW+5i,`CGMaۏ7s@SVQ$KCR/jqgT]EDnr(.$G%T wRցZ >)BGDȉ~lsDxIO fq\ he I 5"0-`2O5PPI[17@ݯtނxCҨnXzw 3u<ãQ@V&>U>$IA߹s3j-B]ުW@&̐sfzJ)ݜjWW硽RIp}#JTw@Qr ؋3# MM2Drs"go&N@.Slc#5P<:Y!ꬕqE1Ei2ݏ'䖖 Z. =/\>UH[>Z%t!I"GSw"qEkS뱸pgٺi|#jV>|NNwx%Pӱm5[ )~M;.5/YEk#ɥzS xTr [8Z8*xvQ2pdϗÞO^8Zc/5Bζe YqyH9 RgTj:Y³GrТE`c*<_(->/(6@|v%uk%c:TqGT0qayj?'['aӊز<ՓkqpM 9UH=NDxB[h+yP Ƕ$aW*GImIdx!CtK!hM+K23a\>꣼Yƃvt7+.k'։Lc jz4F{Ro)XdR^` 4SqH/ uhcR&ӰrΓpBh:`}8gol`,`#M^rJд\Y ?w<7ٿb2k 86^r:/dfWsNoϝl$~X}IAen:E8,S%x{!!\PS㸋q>N0Ƶw My$/Okښ[`FZiY^1G{/µȢ}D2Xb13VzUAiArϱ^C)q3үvzbi>J3fw.ѐk$Ykf$g6,BRB endstream endobj 1074 0 obj << /Length1 1647 /Length2 15459 /Length3 0 /Length 16318 /Filter /FlateDecode >> stream xڭsx_&mucv:wl۶mwl۶mΙ3g<׳^uWպkյ72!PƉ gnmdk-g%C4ut2`DN6N@n: 403`"vfNJU%u*`_OGsS5/2p2ḼyMIr@" ̍2F@G `r 9 v@#n@7#?-`m`0u0q{Ns#+gk7WBvwX)::999FUwNfNv4 lM45raiN6'? csG;+9+ gGs̀45p0::gz;;+ykheB7ئ6 􋤍-vcg\: zoƶ6Vc ,ߐ;DoEyo>Wjqg++9 9;h lg @ϰ2p3p̍?V'['BBtLll6;̝&V_vUc :߿NS137G C@Z_Uak߮pRqg$uY[JX Ic`{^G.fV"bϵ@ ~1#[Ho/?_5 V] hlkdT;<)6lWڠRTWmU^L8tnq(E}4ڋiEѓ&+@"9 `-EHPYbgT;ڛTT-yğ`qy#q)C'ccZщZ[x~Axbpldx&''(]Յ-Ù߆8Vcُ>pZG8ձaͬfL/^Kcr/yFJ5Ea5K%R O@լG|Ri?Nb/zB~plO4aMt_CE[dD4 UG|Kajeƒ ;z3 i%n|yoAo^# O-L='"]ؿrgAsxߒcW /t=jgdKEZ +fVUBeeSǙ+"菶I-/X|)E,n`݁])11DU޴fS]F|ŠD/IB ˮ!v=rLt*G-B4rzDtn^] t}\~pfIy]S#2 XQ^==]L&"&StkG}OHsd]X+F؄#KSMܩOYP%5ﮒFwu>R^O3%8О!t֟pMZ0r^8ݬk~XWό=1F.$O ep^_ liaH*#WoU(}"ЗyT,S/յ>ӬʑuXX`oDz &(MEo%ߝpc!7APd'K ='(oNB;? m 3G:x8hSXV=Q-%&ͻ(QiNV0Mq$Js! 5(28BgK"w㻚[a[;41V\L³z죭PΡęh$RSbُ{\6#=s,1!ϓFV*D`^u{F`3.alh’9sIKfM qIdTas~fhtDג">x7'MGk _}g{v{u٧fajyT<9S}\/=uRX ҇dNl3|ؕ\6E6VK3-xf*MW|XӱB,*Ba&H$,ӇnzzBZa+ҰCN4$]34 &(A=C\4lnԢԭ(ڍl~H?ٮi%nH(F$cM]|J! )v3 X0weI#o.''v}h%CGi&{YuGı\/ rPMɐ=rsG# I#Asf85ʬetÅ-2%`)Yf쬅  -{ Z\LhjB2\?peޡ?/"X/iv RڀN  G {P; zC:X;{2IX|3 {YE8 egX=Ht_?mȓQy1A a /z~(gr"2ѠorלU*ݙp "Nxz;Q=`ܾʼnZ}j'Yx)lpXm1/tDˆenv-x`iЪyē:q/藩3&a|P-} - ƅ iG_!siЊiRL'sAŃC.G8`xs(p?U?Wd Z {[J P2ԋӝ?Ogl=Ʋ/km3eܮ[~g.O ,"Wxr *JY!~t }5D)(sv/}?Ko\3rPf O'"WLbŢyZ*D*ATMgjTDŽa}Q~ɗz0F&@F/dU{J@fvzMZ6]q*hWFɒ%$* Cj $6| 4].q_s RJc{Ejw.;t26TYcWXLz]0"|go/PA[,n.7Ux$un68փd-#)3 '5}Dۿ}H-<MgV SyOF8gq&[[qCJٽ/?B_1=$d6MG=J4{g[ qSB$83*a ]2Y4PwX2G/7IGY$,2X0WZNq.sjKˮy֭q >]dPwuOK_-桫B=Y"oֱ gy"j ]OHl!.DJgUySv!LXM( ,MqԵBdUdQ?嶃0ifw{BFAvMaGE""8L4J!-m|oK)]أ:8CjA;GjKfxU+QO߉=҉~^~ep@dBPւڥKv~pQRY5Yk%mAQ 7ۄ)1g31Qf%/.n3[aw_l觨)x(h8kS|e\̧̲ωv8 dx\XT8ZR`|6I=;E-Sx. tA\Hw4q4dφe,uB9ѥg'>&vˏ[<˨kZzԙwۦR$TKᑂoSI뺟Ml!:Pc|*`+1!G6( \ )gg?0.5c0a\]fwCuU`/xvw`On.NY!~ v!Ԋ`6Y8X;c(X#FPbHCbT^FH~hʙ-N@H_nr&US}="@9xu9Kd'+OSۧ.FŦ O"v8(o)ї,n/$7!ԓg]\;ӝ~6'bJJ7<_3A9!&)ٟ KNu|ONLO ]r;0)"3wGᛃuPX'ߋΜoejti>į=KFqwHO0튡Wq\!Az|=eJ㷹gݘP L=zCB^5B|8fT4 r^) /Ŀn3h]}l H\oВ%aM٨(VK鷛r*`U=>0WSv#zHVML˾$jP Ey0}5}ɲT)~xL-&{PIvwTІÇr*>&կ2Fq2zn1z,e5 bj(U~4kT<+cz e,\vb{mxͦ^GPq:O>,2gl#Q2Q$ݍm?V4juzL.`^5ULծmv tMiuW`$OK՝A,@X$yڄ7p߇BA7ʅw+d6GP9khw1# ɎmW(8hZ{1rU!?yh"j;k7%`Sx\Iʹ!p'bގR0gwi,փ[uhgD#zD:0E ܖǎPɈRҭDu~fl_l1mmF#ېT04mEyܠy~q/5QZ)W=XZфθ&/7B͔YB# %W$8%e q~q^}Ssr6w=; G*rN=;_ִUwgLhM"3&f ߽OmXpnکԥdž]0*aH̭Gpb1Qiʲ]?lb,!D ldcrƃ>NF(6KuX0#J9qȄaP%Q"1oyL MiߞOİbֆonYw<[! ޑ?IHk^%GPM ҐIXRݦtK bc70沤ۋJ 0,E,_mIq*咖6aV.Q9<ZD(^Vc4moro@jdXcht~C$\-c [>o2p2-> e@ 4e͸G~-pI})#Fo0:}-.b~E3K7DCrXq-N8pj^dyf7 )\1Ѐo>qnxF``H*5unmW+OqʊY>,};B|e$lOfqٺqɠhcּ$4Ljl@JK^}Wcz5 [UM3CӅwjz5 =; .Ie_xq<B[Ў.~qđ b`%3"OR$Al@ ¢zs]x__G=V+_>E XђQBt#)\ma$r9)7*rb0GR@uAZnNjtZ,>KiВ6|< 7R^]E~B[P%  ZUUGLD{u`wcFa]FJ GTn &l!"fFx`F@Mqq3HF\ OAB] ҇xԙ` !={7:otZX;G\*(!0vV( hʖ_UG%<]Rd1y9!b͙Eah#sM;U9зu9 "{-3mx6yޅFv.5a/>*~ozҸƭS.5nVl/,јX04yʻ̪M^4uDs \'>kȒjSxA/"Ͻ.a HZZVSYZJ|(mH:V|@OMpJF{, sx =mX=-% #^K_r^%+nDF|PRߟ^;:DXֹٿԢm)B\~'^w!~ry:7]T %Ҭ%By};$PQ$ѐb ]X"=&w:8#kgI1vƥ&J#};Qw|M pvUmb\i"#*vHP[{=+M !MF|'*;GPT|yD&!¥;07/ Jq/jg'JߕI[6" /)/׉{F_1,0cp":Ġ,C[n**g0apKqo\Ebna;ǷK7@Ԫ=<PBuOɛ^V|T!x=t6PJ썵=@o,|(Dg6A e]DF9h5k st4RG "k㉮X-<63|_R ر V gg,R ܵRq͎FQ-g9_FάVk2wYG(>e5ڣsޑrϸۙҾs-q"aWyW!ETx`%X)! i}q &qD^zMnzy*TS/k۽(UWEOpyYyNͧrmDlWG襂z"=lγa>":]$oA5LvYH6/_R{F!V Qwj C(H\3GM%+߰ y%xGUiP9vHHX8 oCk3[$:JEXº~IJ?Xe 90^v-,_Z2${`u)YklfS[VT4,˖ FL >;_d涜7߂W=S[PC=D}]؈ y89; .;e4t-JEųD7 ԻhMX4M;1u{ ;> u >N,8}_FZ6ޛo':dhּ|W% U#d{($Á5,)ZȐ9ݽ%6F53s(Jb 76z"x#~/PEc &s]l)|fPꈓڨ5B1K>>YyecG|\} 37ݺ)[Q&ѩ5Z }c8Kh}GxH1A?t軤O14i6r d0=q 9,SISi&E$$51{…:NfdΊג;4P%Y+*u%q`[૪9W4Nau͒8}bqylaEkξHFFsh =hI' ~;[QRƠ30Q>pꈚ ͑51; b}ϴQ]g6Uv z`͈J/Jv^e:PMw^uDlDVz@KLdE\z 'V`ʵLs )D5\e 7\$rl%3eߒTD]z)nODž =la\ ̈Dj N8B5>,p[,i R(IH2ӌj16Y\?n(C WbeД.ИZ=hMu]9:jؒ=q :]qӖ]_~|MRGvNRD[I\ѝAOO>xA3=I]p=xTbr~Yei5-ҝs=zb;ЎcuK}eFk`abj_c.Vzgu^WS{)6T:ǙuXK]Zc8= ^iV%,)bIKG49^-^aj69%WhoB5](=hiQ3Y hJB,Z 9ȧ 4̜}柭arHm? w8oQ"㍌ pg]ծ2lQP:Κ@5-OfAR VGXLjět|3?"g2/&n5>_6eI5S7T Euy.OyzI6d8ĤD&f;f)3tԤD ?dրC62kmնr\i5Zs~= 6 8dtesexỮA㕸2&ə*M!O ݆GW6bM"$e8un1G"(';^M=SrUEzḏ}Ҭ!bMPڴvۗ?8y0[b],^TOC^~XR˃l"]F0clCSo~Ãi2%~Od𰻾0ae1(h yf&. JL8+B#G ,@z'R-u*EXs‘b*h,7VQsfX \z(H*\|%2|hq`(RIFQ2zP%M4u$ ne s]ΐE,2"6$<ȼ7079{iOur2<& v07EI֍#ӚoADU6OYZPR`@zӟC(dT)f%j@ŧGnzd+XmZ5(U+r1$11GqbN\ En|7ge^Ysg =?%&!UD[C^8r+V0N̈́w#Ēc;S3&윯BU۲0,<$yٱ,@=d@Tmxi ",}f\&};0e 7jE7 @fwF` ƌI:KHEPgSOˌظ"@V{"(Mu7'\R *#~ƝwnwC5zȰ''P- Xnj5@2[\.(csQөx)o$JqiPq~ ELCXqSMcYorWp#ńۯY^N1=,n]ޟ Sm"(\g3Naa_'FJ)J]_{cGќ:٩ߠ)Hผh"L# ƬƬL0-$B . H˅Ud跆J*acO"tiӰv6۳T:秪^FSWV<34E&xOf>}VEE .ݟp2GgXAmX{8fo=jpz_A@ߍ\`<8RNԷu~eOzhlacK 鐀^vbBhXJ2B @$QP: +%, viDሳA|~ꌴߡY}IZ&MSyG|Mښ &jTG8:=5P\`|"UAiAŷ>*jSq1KOX^rV+y9VJX[8yOTG=83Y->L$)ƂU5%A67?~FHAH/'i%Ɠ;,L0 .(悱~-қ&+2E\]4Qt%GR C;]T4t3p >X+3gtLEN:;.F4b8ʞq:"3A#h}|Rs#ޞ7d[h.aoF.ّ!&bd[bS`:C֜F{ .ED[G1 2P)x9UU|ieW4J1|%蝙{MLb`k'߯=wbX£I1 5OE5毦,{n_S`GK*/IǙZo:(+f"{@AQ:}4p[ Yr_!HGŚF#o07>xMTEa)VǬ!${7gk!Ӗ,\-h Jc xζ@+)DXTGyA 2Cڻ #5MǏ/84Thșw;dr$>⇘d*枟1M4>Й< oeUX\/i,9<5L_oFwwBetsdn,3 Z6tαv8j0n0!bA GKE|ӲW۲z M+u߂Q%!$OߡGEn,VSJRoIbDN&6չ&aו=ӖwOHYQ:X9gá.t%jNe6NlTPf\~^RIs0qK+7{T%Wсn GrBTpy mt<=mU e)bZEy ^<@$X- ccFc&NB)3ujf$)իAƷ|2U AGu(瑉LRºF[r~cի<>/ ]䦟$x ;ԕ>g0d { ?d7=6 z=8vJ50> stream xZioF>?"}A˶և";15CI\! EɊc,@lVz93V ˆUpM,DBq41N$Ş {0a.o=Ru`k>B- /<8f 0')l lJt  D> QO=pD>%,c%">|[1Fa!J =hAڙg#3Dhi"5x;cCKB@c  9мEt ]J,'sc(i-r ErJ3HTpr% h:~@s`0GNi 2iFNc@&M2Y!s PLs8<q{YI5Pb(Q0Xb`0X\`d X0. Ԩ+ gL39cB1=1 @BH"(D jk;z˝vLA>muwP]J u ԐX%cncIұ9qз-'cCgB:xd;h#x3E GUc|=6/a^Ǻx 9\D+cD܎ x6=ۉ9O Ĉg8Pń"vUPj##]T^ܭ*@@^$ 2Y"2TځͬZRWnIVb[|n[cOmw椭vTfgL } םF<vy]r`m S]}o'K,>+VM>h"5eem.2גbʠI?mWˏ<.LNm'Y7'ܾ^?._Zyr6}麄-b~TzXB<|b]}+G=/F5lD|M؈>,U蠧t麘Viݓu9;#: WA^˓rMA}>7}Bg9=?#czB__)oﴠ\qUx3eiu5\jss,崼Xr]_v1t1YA'tNHIC@|)83zA/%Xl^.+zӊS:-W+:s:%ofNu9 X%"isfErb]NΧP@ĒHWbuEt},KcA7t3bY~rgL @\G?t;<}G|:){7 NWxMYxm%ZT\ސmN7٦lgVTKz/RTNj\N3Id k7 &0o+֚GϣitN[O /N _Abb. 1Cn  kyYEyD Eu5dC);Bڭ?5ܭ2r2_.*91YY_,?aqyuマ;`7gҞC*zI3Q]a$LToqAEɓ1ț@Dosg|;qU EþYئv{jM0n:Qz90N޳>W]\y\["3H i?6Or"}{U׬P "wo `\1e 6Dx.sa%"ԫXrMe&ڽJnk=_rS%οug]3j3y4T$y#Ta\vM ^w"}c:F*}4j޿& Q4m[nj9_.^2_J3SzP-Wk,nhu~&Uz/aoz< ނgW7K܆g +ј}0iJƶ>T#@vP}F9,rX@ İ#@4aDG'hOD :>X#u}F6,`~ߡk =A]xûiw6龇f^g]pwomնR;vfʇ( endstream endobj 1080 0 obj << /Type /ObjStm /N 100 /First 995 /Length 4182 /Filter /FlateDecode >> stream xڝ\Mo$ܬߤ`HK 9$9fzWχvfׇ=UwdnXz,9#Y՝S;mgl'EdӺwQ;%;k输dyg;aC'd'z);N8轈]R5W0&|╂DgFXIקѹ^hG+21؊zd1>^fouN#,K#\g|D$"(^ Z"'VT6$Y2!"atθ-9oy ZS+tF\?Dƈf:.ؾsɃ3C.VMe4+fVY. ZfZ?z6%ᄖ+1iXMXM=Њyt*@!MD!RE `q6gSq )܉E%i& qu8[id^ $MM/U ˜4:vVAw4 /Ii VZlpĺ2Dgs}J dK)4?J@aNac2HlZl9>|tٍ1-f÷:6%.v4~A=H%fpSCfH6 ĐC"2eXjHsClH7 Ij/R7C}ːս!}[7lS[M5[7UlE l[Qulu[5lU[M%[7elI l[Pelq[4lY[M)֢kif#RXI#u؏/r׉eߋ *"PX"Q (˴.)ڿn%pꂃ] DJeL?=>゙MDj$5SED^p/+2w-d`ϛ)2Y ,YGr CeJ0Dϋou&[t(~xؿ\˕ZOi05rF^3TD .PN; a+&T^ǏOǟ~Q*~/Ẫ/šluX歨jP8)*>>֢,|M|VƈV66n $JߚYTӋi3JO9MoVHs.LcB6?!B_>"H{~{I|ӄF_&o8R7w,x} ]گd݋ܵ}p]y ]Ġ@D%ˢ=y}fd7Aez|܍ =/k~w=C~~2`Dut9Yq~TBIF|\N燇gs?=g2By:lGFii$Eu hrbHmXO;y]8n徿 gi&a!aO%DA%tޛ"nPWC@bBbAC.YoZb/ -[xXsl]44lpv9p9Yҥ~~N1Hq:1Va4[. 5 Ȍal ݤ/j0 6\~+H;v, %iϖXo`4i^l> [ Rq fł,ۤZẠh,Z.)DOE "pârYpV,*쀘VuݒB&pІ @l[{@֪?n?I!<,u=-ߧ +_].wA??beO/(b),JJp(j&b%TH9"e0|EV;)|C6R9hz@c?*3E-Ex&8beDC1Y-=4 /4#EP0-Yza b[uI3bQ3t4#p :PwZ UR4]xPY4=J6YID vڃhVgl 6U~/mGp!f F] J=ư h[1&0vK\ư*c1^ŵ"ȏa-` =Aa ց^XQgnA[ cU0M `ٕŰb؅$S/z:=eY<5k=eDjwJ >Zfv2_wB'OXu@ @8Ja)Q3LPsm{~/6&}nkZK_6V_^jȡJrm7mkl_6dnп ]קI >75_)|g9H9._4nDNd^aW>O#s42G#s42,+ҿɜ(1zsQʀ{|;Os:s:;'%C.s:Gs9Y\909ӬtN endstream endobj 1169 0 obj << /Author()/Title()/Subject()/Creator(LaTeX with hyperref package)/Producer(pdfTeX-1.40.16)/Keywords() /CreationDate (D:20210406062941-05'00') /ModDate (D:20210406062941-05'00') /Trapped /False /PTEX.Fullbanner (This is pdfTeX, Version 3.14159265-2.6-1.40.16 (TeX Live 2015) kpathsea version 6.2.1) >> endobj 1165 0 obj << /Type /ObjStm /N 4 /First 34 /Length 202 /Filter /FlateDecode >> stream xuI 0sTA6P7W\N!h(`oAۗ7DCa)` $ ]bZ֒ <3KU)cVr%Chy1G·g2~Mav(xoQHmJsaĭ) =)Hn^⽴owY5WIZz -!U endstream endobj 1170 0 obj << /Type /XRef /Index [0 1171] /Size 1171 /W [1 3 1] /Root 1168 0 R /Info 1169 0 R /ID [<955DD2C07CE0C51FF205C3C34A49D6B2> <955DD2C07CE0C51FF205C3C34A49D6B2>] /Length 2670 /Filter /FlateDecode >> stream xI]Yo^oo%7}r>KBMDBMāP  'BPG@堰E "RG䟷׽gsUUUUUJ،UwW n7cbNib/Nb΄Ym#HlaL0OlX\?@l{ a6ŰDl-ebGv bV]G] kY^֊eZGvmbOo 7fb Vb vKb;`MK 0MbsŞbkŞb[Ğbִb'cbqϏ 'bOf}3pVl=sp^l] pQKpYnۆWP5>X7Ɨoź\7+vP,/Cm<ˆg#(Vэ^ ӄ޸Dlf.jc-!_)N"vc^PT `p!`a G\j\x7f9 spqx .7&L\hx7Ԑ9-iapI xtgS j֒4g_y!ws%Uu\% 4UVit< 4^ hCG'ciiᗆ_h?Q+ 甞MJFU (Qf6i_ A ) RpDሢ((gkxx%E_Wf(-R-JVWAJKaRkP  e<IKRԗrTrPPP o^e"cf>(,PX( 3+Zr3?k*ޙěBQS3 .^&Li7b$&lJtKb8La̅y0BXa ,eVJXa a-6& [`+l@ɝ0`7 `}A8@c0Spz,p.% W*\Oހp n ><}#M| 6)&%$3U3s)0Δ!Wt#444444444444444444444444444ϑ444444444444444ϑ4444444Qhiiiiiiiii#iiiiiiiiiiiɝgtq}s& fC8u 7O 퀝)=z'.տ>vo_S+u{kO=>So}Zk 8\ Z]kgUuRUŤ^~jAz=7 BkV{:1=V0@P`:N9Fmj=avSyg c0kZ[qztu[ڒq$\ p p4YyډWG>ɱb}vX?gq&!N\H9*~ BLUNI>}W0& endstream endobj startxref 177871 %%EOF dbplyr/tests/0000755000176200001440000000000014033043057012707 5ustar liggesusersdbplyr/tests/testthat/0000755000176200001440000000000014033054372014551 5ustar liggesusersdbplyr/tests/testthat/test-verb-pivot-longer.R0000644000176200001440000000671314004012136021227 0ustar liggesuserstest_that("can pivot all cols to long", { pv <- memdb_frame(x = 1:2, y = 3:4) %>% tidyr::pivot_longer(x:y) expect_equal( pv %>% collect(), tibble( name = c("x", "x", "y", "y"), value = 1:4 ) ) expect_snapshot( lazy_frame(x = 1:2, y = 3:4) %>% tidyr::pivot_longer(x:y) ) }) test_that("can add multiple columns from spec", { # add columns `a` and `b` sp <- tibble( .name = c("x", "y"), .value = "v", a = 11:12, b = 13:14 ) pv <- lazy_frame(x = 1:2, y = 3:4) %>% dbplyr_pivot_longer_spec(df_db, spec = sp) expect_equal(colnames(pv), c("a", "b", "v")) expect_snapshot(pv) }) test_that("preserves original keys", { # preserves `x` pv <- lazy_frame(x = 1:2, y = 2, z = 1:2) %>% tidyr::pivot_longer(y:z) expect_equal(colnames(pv), c("x", "name", "value")) expect_snapshot(pv) }) test_that("can drop missing values", { expect_snapshot( lazy_frame(x = c(1, NA), y = c(NA, 2)) %>% tidyr::pivot_longer(x:y, values_drop_na = TRUE) ) }) test_that("can handle missing combinations", { df <- tibble::tribble( ~id, ~x_1, ~x_2, ~y_2, "A", 1, 2, "a", "B", 3, 4, "b", ) df_db <- memdb_frame(!!!df) pv_db <- tidyr::pivot_longer( df_db, -id, names_to = c(".value", "n"), names_sep = "_" ) pv <- pv_db %>% collect() expect_named(pv, c("id", "n", "x", "y")) expect_equal(pv$x, c(1, 3, 2, 4)) expect_equal(pv$y, c(NA, NA, "a", "b")) sql <- tidyr::pivot_longer( lazy_frame(!!!df), -id, names_to = c(".value", "n"), names_sep = "_" ) expect_snapshot(sql) }) test_that("can override default output column type", { expect_snapshot( lazy_frame(x = 1) %>% tidyr::pivot_longer(x, values_transform = list(value = as.character)) ) }) test_that("can pivot to multiple measure cols", { df <- lazy_frame(x = "x", y = 1) sp <- tibble::tribble( ~.name, ~.value, ~row, "x", "X", 1, "y", "Y", 1, ) pv <- dbplyr_pivot_longer_spec(df, sp) expect_equal(colnames(pv), c("row", "X", "Y")) expect_snapshot(pv) }) test_that("original col order is preserved", { df <- tibble::tribble( ~id, ~z_1, ~y_1, ~x_1, ~z_2, ~y_2, ~x_2, "A", 1, 2, 3, 4, 5, 6, "B", 7, 8, 9, 10, 11, 12, ) %>% tbl_lazy() expect_equal( df %>% tidyr::pivot_longer(-id, names_to = c(".value", "n"), names_sep = "_") %>% colnames(), c("id", "n", "z", "y", "x") ) }) test_that(".value can be at any position in `names_to`", { samp <- tibble( i = 1:4, y_t1 = rnorm(4), y_t2 = rnorm(4), z_t1 = rep(3, 4), z_t2 = rep(-2, 4), ) value_first <- tidyr::pivot_longer( lazy_frame(!!!samp), -i, names_to = c(".value", "time"), names_sep = "_" ) expect_snapshot(value_first) samp2 <- dplyr::rename(samp, t1_y = y_t1, t2_y = y_t2, t1_z = z_t1, t2_z = z_t2) value_second <- tidyr::pivot_longer( lazy_frame(!!!samp2), -i, names_to = c("time", ".value"), names_sep = "_" ) expect_snapshot(value_second) cols <- c("i", "time", "y", "z") expect_equal(colnames(value_first), cols) expect_equal(colnames(value_second), cols) }) test_that("grouping is preserved", { df <- memdb_frame(g = 1, x1 = 1, x2 = 2) out <- df %>% group_by(g) %>% tidyr::pivot_longer(x1:x2, names_to = "x", values_to = "v") expect_equal(group_vars(out), "g") }) dbplyr/tests/testthat/test-backend-impala.R0000644000176200001440000000161314015731174020504 0ustar liggesuserstest_that("custom scalar functions translated correctly", { local_con(simulate_impala()) expect_equal(translate_sql(as.Date(x)), sql("CAST(`x` AS VARCHAR(10))")) expect_equal(translate_sql(ceiling(x)), sql("CEIL(`x`)")) }) test_that("custom bitwise operations translated correctly", { local_con(simulate_impala()) expect_equal(translate_sql(bitwNot(x)), sql("BITNOT(`x`)")) expect_equal(translate_sql(bitwAnd(x, 128L)), sql("BITAND(`x`, 128)")) expect_equal(translate_sql(bitwOr(x, 128L)), sql("BITOR(`x`, 128)")) expect_equal(translate_sql(bitwXor(x, 128L)), sql("BITXOR(`x`, 128)")) expect_equal(translate_sql(bitwShiftL(x, 2L)), sql("SHIFTLEFT(`x`, 2)")) expect_equal(translate_sql(bitwShiftR(x, 2L)), sql("SHIFTRIGHT(`x`, 2)")) }) test_that("generates custom sql", { con <- simulate_impala() expect_snapshot(sql_table_analyze(con, in_schema("schema", "tbl"))) }) dbplyr/tests/testthat/test-escape.R0000644000176200001440000001027414002647450017116 0ustar liggesusers# Identifiers ------------------------------------------------------------------ ei <- function(...) unclass(escape(ident(c(...)), con = simulate_dbi())) test_that("identifiers get identifier quoting", { expect_equal(ei("x"), '`x`') }) test_that("identifiers are comma separated", { expect_equal(ei("x", "y"), '`x`, `y`') }) test_that("identifier names become AS", { expect_equal(ei(x = "y"), '`y` AS `x`') }) # Zero-length inputs ------------------------------------------------------ test_that("zero length inputs yield zero length output when not collapsed", { con <- simulate_dbi() expect_equal(sql_vector(sql(), collapse = NULL, con = con), sql()) expect_equal(sql_vector(ident(), collapse = NULL, con = con), sql()) }) test_that("zero length inputs yield length-1 output when collapsed", { con <- simulate_dbi() expect_equal(sql_vector(sql(), parens = FALSE, collapse = "", con = con), sql("")) expect_equal(sql_vector(sql(), parens = TRUE, collapse = "", con = con), sql("()")) expect_equal(sql_vector(ident(), parens = FALSE, collapse = "", con = con), sql("")) expect_equal(sql_vector(ident(), parens = TRUE, collapse = "", con = con), sql("()")) }) # Numeric ------------------------------------------------------------------ test_that("missing vaues become null", { con <- simulate_dbi() expect_equal(escape(NA, con = con), sql("NULL")) expect_equal(escape(NA_real_, con = con), sql("NULL")) expect_equal(escape(NA_integer_, con = con), sql("NULL")) expect_equal(escape(NA_character_, con = con), sql("NULL")) }) test_that("-Inf and Inf are expanded and quoted", { con <- simulate_dbi() expect_equal(escape(Inf, con = con), sql("'Infinity'")) expect_equal(escape(-Inf, con = con), sql("'-Infinity'")) }) test_that("can escape integer64 values", { con <- simulate_dbi() skip_if_not_installed("bit64") expect_equal( escape(bit64::as.integer64(NA), con = con), sql("NULL") ) expect_equal( escape(bit64::as.integer64("123456789123456789"), con = con), sql("123456789123456789") ) }) # Logical ----------------------------------------------------------------- test_that("logical is SQL-99 compatible (by default)", { con <- simulate_dbi() expect_equal(escape(TRUE, con = con), sql("TRUE")) expect_equal(escape(FALSE, con = con), sql("FALSE")) expect_equal(escape(NA, con = con), sql("NULL")) }) # Date-time --------------------------------------------------------------- test_that("date and date-times are converted to ISO 8601", { con <- simulate_dbi() x1 <- ISOdatetime(2000, 1, 2, 3, 4, 5, tz = "US/Central") x2 <- as.Date(x1) expect_equal(escape(x1, con = con), sql("'2000-01-02T09:04:05Z'")) expect_equal(escape(x2, con = con), sql("'2000-01-02'")) }) # Raw ----------------------------------------------------------------- test_that("raw is SQL-99 compatible (by default)", { con <- simulate_dbi() expect_equal(escape(blob::as_blob(raw(0)), con = con), sql("X''")) expect_equal(escape(blob::as_blob(as.raw(c(0x01, 0x02, 0x03))), con = con), sql("X'010203'")) expect_equal(escape(blob::as_blob(as.raw(c(0x00, 0xff))), con = con), sql("X'00ff'")) }) # Helpful errors -------------------------------------------------------- test_that("shiny objects give useful errors", { lf <- lazy_frame(a = 1) input <- structure(list(), class = "reactivevalues") x <- structure(function() "y", class = "reactive") expect_snapshot_error(lf %>% filter(mpg == input$x) %>% show_query()) expect_snapshot_error(lf %>% filter(mpg == x()) %>% show_query()) }) # names_to_as() ----------------------------------------------------------- test_that("names_to_as() doesn't alias when ident name and value are identical", { x <- ident(name = "name") y <- sql("`name`") expect_equal(names_to_as(y, names2(x), con = simulate_dbi()), "`name`") }) test_that("names_to_as() doesn't alias when ident name is missing", { x <- ident("*") y <- sql("`*`") expect_equal(names_to_as(y, names2(x), con = simulate_dbi()), "`*`") }) test_that("names_to_as() aliases when ident name and value are different", { x <- ident(new_name = "name") y <- sql(new_name = "`name`") expect_equal(names_to_as(y, names2(x), con = simulate_dbi()), "`name` AS `new_name`") }) dbplyr/tests/testthat/test-verb-summarise.R0000644000176200001440000000625714006531012020613 0ustar liggesuserstest_that("summarise peels off a single layer of grouping", { mf1 <- memdb_frame(x = 1, y = 1, z = 2) %>% group_by(x, y) mf2 <- mf1 %>% summarise(n = n()) expect_equal(group_vars(mf2), "x") mf3 <- mf2 %>% summarise(n = n()) expect_equal(group_vars(mf3), character()) }) test_that("summarise performs partial evaluation", { mf1 <- memdb_frame(x = 1) val <- 1 mf2 <- mf1 %>% summarise(y = x == val) %>% collect() expect_equal(mf2$y, 1) }) test_that("can't refer to freshly created variables", { mf1 <- lazy_frame(x = 1) expect_error( summarise(mf1, y = sum(x), z = sum(y)), "refers to a variable" ) }) test_that("summarise(.groups=)", { df <- lazy_frame(x = 1, y = 2) %>% group_by(x, y) # the `dplyr::` prefix is needed for `check()` # should produce a message when called directly by user expect_message(eval_bare( expr(lazy_frame(x = 1, y = 2) %>% dplyr::group_by(x, y) %>% dplyr::summarise() %>% remote_query()), env(global_env()) )) expect_snapshot(eval_bare( expr(lazy_frame(x = 1, y = 2) %>% dplyr::group_by(x, y) %>% dplyr::summarise() %>% remote_query()), env(global_env()) )) # should be silent when called in another package expect_silent(eval_bare( expr(lazy_frame(x = 1, y = 2) %>% dplyr::group_by(x, y) %>% dplyr::summarise() %>% remote_query()), asNamespace("testthat") )) expect_equal(df %>% summarise() %>% group_vars(), "x") expect_equal(df %>% summarise(.groups = "drop_last") %>% group_vars(), "x") expect_equal(df %>% summarise(.groups = "drop") %>% group_vars(), character()) expect_equal(df %>% summarise(.groups = "keep") %>% group_vars(), c("x", "y")) expect_snapshot_error(df %>% summarise(.groups = "rowwise")) }) # sql-render -------------------------------------------------------------- test_that("quoting for rendering summarized grouped table", { out <- memdb_frame(x = 1, .name = "verb-summarise") %>% group_by(x) %>% summarise(n = n()) expect_snapshot(out %>% sql_render) expect_equal(out %>% collect, tibble(x = 1, n = 1L)) }) # sql-build --------------------------------------------------------------- test_that("summarise generates group_by and select", { out <- lazy_frame(g = 1) %>% group_by(g) %>% summarise(n = n()) %>% sql_build() expect_equal(out$group_by, sql('`g`')) expect_equal(out$select, sql('`g`', 'COUNT(*) AS `n`')) }) # ops --------------------------------------------------------------------- test_that("summarise replaces existing", { out <- tibble(x = 1, y = 2) %>% tbl_lazy() %>% summarise(z = 1) expect_equal(op_vars(out), "z") }) test_that("summarised vars are always named", { mf <- dbplyr::memdb_frame(a = 1) out1 <- mf %>% summarise(1) %>% op_vars() expect_equal(out1, "1") }) test_that("grouped summary keeps groups", { out <- tibble(g = 1, x = 1) %>% tbl_lazy() %>% group_by(g) %>% summarise(y = 1) expect_equal(op_vars(out), c("g", "y")) }) test_that("summarise drops one grouping level", { df <- tibble(g1 = 1, g2 = 2, x = 3) %>% tbl_lazy() %>% group_by(g1, g2) out1 <- df %>% summarise(y = 1) out2 <- out1 %>% summarise(y = 2) expect_equal(op_grps(out1), "g1") expect_equal(op_grps(out2), character()) }) dbplyr/tests/testthat/helper-src.R0000644000176200001440000000244714002647450016750 0ustar liggesuserson_gha <- function() identical(Sys.getenv("GITHUB_ACTIONS"), "true") on_cran <- function() !identical(Sys.getenv("NOT_CRAN"), "true") if (test_srcs$length() == 0) { # test_register_src("df", dplyr::src_df(env = new.env(parent = emptyenv()))) test_register_con("sqlite", RSQLite::SQLite(), ":memory:") if (identical(Sys.getenv("GITHUB_POSTGRES"), "true")) { test_register_con("postgres", RPostgres::Postgres(), dbname = "test", user = "postgres", password = "password" ) } else if (identical(Sys.getenv("GITHUB_MSSQL"), "true")) { test_register_con("mssql", odbc::odbc(), driver = "ODBC Driver 17 for SQL Server", database = "test", uid = "SA", pwd = "Password12", server = "localhost", port = 1433 ) } else if (on_gha() || on_cran()) { # Only test with sqlite } else { test_register_con("MariaDB", RMariaDB::MariaDB(), dbname = "test", host = "localhost", username = Sys.getenv("USER") ) test_register_con("postgres", RPostgres::Postgres(), dbname = "test", host = "localhost", user = "" ) } } sqlite_con_with_aux <- function() { tmp <- tempfile() con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") DBI::dbExecute(con, paste0("ATTACH '", tmp, "' AS aux")) con } dbplyr/tests/testthat/test-verb-copy-to.R0000644000176200001440000000315414002647450020203 0ustar liggesuserstest_that("can copy to from remote sources", { df <- tibble(x = 1:10) con1 <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") on.exit(DBI::dbDisconnect(con1), add = TRUE) df_1 <- copy_to(con1, df, "df1") # Create from tbl in same database df_2 <- copy_to(con1, df_1, "df2") expect_equal(collect(df_2), df) # Create from tbl in another data con2 <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") on.exit(DBI::dbDisconnect(con2), add = TRUE) df_3 <- copy_to(con2, df_1, "df3") expect_equal(collect(df_3), df) }) test_that("can round trip basic data frame", { df <- test_frame(x = c(1, 10, 9, NA), y = letters[1:4]) expect_equal_tbls(df) }) test_that("NAs in character fields handled by db sources (#2256)", { df <- test_frame( x = c("a", "aa", NA), y = c(NA, "b", "bb"), z = c("cc", NA, "c") ) expect_equal_tbls(df) }) test_that("only overwrite existing table if explicitly requested", { con <- DBI::dbConnect(RSQLite::SQLite()) on.exit(DBI::dbDisconnect(con)) DBI::dbWriteTable(con, "df", data.frame(x = 1:5)) expect_error(copy_to(con, data.frame(x = 1), name = "df"), "exists") expect_silent(copy_to(con, data.frame(x = 1), name = "df", overwrite = TRUE)) }) test_that("can create a new table in non-default schema", { con <- sqlite_con_with_aux() on.exit(DBI::dbDisconnect(con)) df1 <- tibble(x = 1) df2 <- tibble(x = 2) db1 <- copy_to(con, df1, in_schema("aux", "df"), temporary = FALSE) expect_equal(collect(db1), df1) # And can overwrite db2 <- copy_to(con, df2, in_schema("aux", "df"), temporary = FALSE, overwrite = TRUE) expect_equal(collect(db2), df2) }) dbplyr/tests/testthat/test-translate-sql-quantile.R0000644000176200001440000000133214002647450022263 0ustar liggesuserstest_that("quantile and median don't change without warning", { expect_snapshot(translate_sql(quantile(x, 0.75), window = FALSE)) expect_snapshot(translate_sql(quantile(x, 0.75), vars_group = "g")) expect_snapshot(translate_sql(median(x, na.rm = TRUE), window = FALSE)) expect_snapshot(translate_sql(median(x, na.rm = TRUE), vars_group = "g")) }) test_that("median functions warn once if na.rm = FALSE", { expect_warning(translate_sql(median("x")), "Missing values") expect_warning(translate_sql(median("x")), NA) expect_warning(translate_sql(median("x", na.rm = TRUE)), NA) }) test_that("checks for invalid probs", { expect_error(check_probs("a"), "numeric") expect_error(check_probs(1:3), "single value") }) dbplyr/tests/testthat/test-sql-build.R0000644000176200001440000000030314002647450017542 0ustar liggesuserstest_that("rendering table wraps in SELECT *", { out <- memdb_frame(x = 1, .name = "test-sql-build") expect_snapshot(out %>% sql_render()) expect_equal(out %>% collect(), tibble(x = 1)) }) dbplyr/tests/testthat/test-verb-mutate.R0000644000176200001440000001337214002647450020113 0ustar liggesuserstest_that("mutate computed before summarise", { mf <- memdb_frame(x = c(1, 2, 3), y = c(9, 8, 7)) out <- mutate(mf, z = x + y) %>% summarise(sum_z = sum(z, na.rm = TRUE)) %>% collect() expect_equal(out$sum_z, 30) }) test_that("two mutates equivalent to one", { mf <- memdb_frame(x = c(1, 5, 9), y = c(3, 12, 11)) df1 <- mf %>% mutate(x2 = x * 2, y4 = y * 4) %>% collect() df2 <- mf %>% collect() %>% mutate(x2 = x * 2, y4 = y * 4) expect_equal_tbl(df1, df2) }) test_that("can refer to fresly created values", { out1 <- memdb_frame(x1 = 1) %>% mutate(x2 = x1 + 1, x3 = x2 + 1, x4 = x3 + 1) %>% collect() expect_equal(out1, tibble(x1 = 1, x2 = 2, x3 = 3, x4 = 4)) out2 <- memdb_frame(x = 1, .name = "multi_mutate") %>% mutate(x = x + 1, x = x + 2, x = x + 4) expect_equal(collect(out2), tibble(x = 8)) expect_snapshot(show_query(out2)) }) test_that("transmute includes all needed variables", { lf <- lazy_frame(x = 1, y = 2) out <- transmute(lf, x = x / 2, x2 = x + y) expect_named(out$ops$x$args$vars, c("x", "y")) expect_snapshot(out) }) test_that("queries are not nested unnecessarily", { # Should only be one query deep sql <- memdb_frame(x = 1) %>% mutate(y = x + 1, a = y + 1, b = y + 1) %>% sql_build() expect_s3_class(sql$from, "select_query") expect_s3_class(sql$from$from, "ident") }) test_that("maintains order of existing columns (#3216, #3223)", { lazy <- lazy_frame(x = 1, y = 2) %>% mutate(z = 3, y = 4, y = 5) expect_equal(op_vars(lazy), c("x", "y", "z")) }) test_that("supports overwriting variables (#3222)", { df <- memdb_frame(x = 1, y = 2) %>% mutate(y = 4, y = 5) %>% collect() expect_equal(df, tibble(x = 1, y = 5)) df <- memdb_frame(x = 1, y = 2) %>% mutate(y = 4, y = y + 1) %>% collect() expect_equal(df, tibble(x = 1, y = 5)) df <- memdb_frame(x = 1, y = 2) %>% mutate(y = 4, y = x + 4) %>% collect() expect_equal(df, tibble(x = 1, y = 5)) }) # SQL generation ----------------------------------------------------------- test_that("mutate generates new variables and replaces existing", { df1 <- memdb_frame(x = 1) out <- df1 %>% mutate(x = 2, y = 3) %>% collect() expect_equal(out, tibble(x = 2, y = 3)) }) test_that("transmute only returns new variables", { df1 <- memdb_frame(x = 1) out <- df1 %>% transmute(y = 3) %>% collect() expect_equal(out, tibble(y = 3)) }) test_that("mutate calls windowed versions of sql functions", { df1 <- memdb_frame(x = 1:4, g = rep(c(1, 2), each = 2)) out <- df1 %>% group_by(g) %>% mutate(r = row_number(x)) %>% collect() expect_equal(out$r, c(1, 2, 1, 2)) }) test_that("recycled aggregates generate window function", { df1 <- memdb_frame(x = as.numeric(1:4), g = rep(c(1, 2), each = 2)) out <- df1 %>% group_by(g) %>% mutate(r = x - mean(x, na.rm = TRUE)) %>% collect() expect_equal(out$r, c(-0.5, 0.5, -0.5, 0.5)) }) test_that("cumulative aggregates generate window function", { df1 <- memdb_frame(x = 1:4, g = rep(c(1, 2), each = 2)) out <- df1 %>% group_by(g) %>% arrange(x) %>% mutate(r = cumsum(x)) %>% collect() expect_equal(out$r, c(1, 3, 3, 7)) }) test_that("mutate overwrites previous variables", { df <- memdb_frame(x = 1:5) %>% mutate(x = x + 1) %>% mutate(x = x + 1) %>% collect() expect_equal(names(df), "x") expect_equal(df$x, 1:5 + 2) }) test_that("sequence of operations work", { out <- memdb_frame(x = c(1, 2, 3, 4)) %>% select(y = x) %>% mutate(z = 2 * y) %>% filter(z == 2) %>% collect() expect_equal(out, tibble(y = 1, z = 2)) }) # sql_render -------------------------------------------------------------- test_that("quoting for rendering mutated grouped table", { out <- memdb_frame(x = 1, y = 2) %>% mutate(y = x) expect_match(out %>% sql_render, "^SELECT `x`, `x` AS `y`\nFROM `[^`]*`$") expect_equal(out %>% collect, tibble(x = 1, y = 1)) }) test_that("mutate generates subqueries as needed", { lf <- lazy_frame(x = 1, con = simulate_sqlite()) expect_snapshot(lf %>% mutate(x = x + 1, x = x + 1)) expect_snapshot(lf %>% mutate(x1 = x + 1, x2 = x1 + 1)) }) test_that("mutate collapses over nested select", { lf <- lazy_frame(g = 0, x = 1, y = 2) expect_snapshot(lf %>% select(x:y) %>% mutate(x = x * 2, y = y * 2)) expect_snapshot(lf %>% select(y:x) %>% mutate(x = x * 2, y = y * 2)) }) # sql_build --------------------------------------------------------------- test_that("mutate generates simple expressions", { out <- lazy_frame(x = 1) %>% mutate(y = x + 1L) %>% sql_build() expect_equal(out$select, sql(x = '`x`', y = '`x` + 1')) }) test_that("mutate can drop variables with NULL", { out <- lazy_frame(x = 1, y = 1) %>% mutate(y = NULL) %>% sql_build() expect_named(out$select, "x") }) test_that("mutate_all generates correct sql", { out <- lazy_frame(x = 1, y = 1) %>% dplyr::mutate_all(~ . + 1L) %>% sql_build() expect_equal(out$select, sql(x = '`x` + 1', y = '`y` + 1')) out <- lazy_frame(x = 1) %>% dplyr::mutate_all(list(one = ~ . + 1L, two = ~ . + 2L)) %>% sql_build() expect_equal(out$select, sql(`x` = '`x`', one = '`x` + 1', two = '`x` + 2')) }) test_that("mutate_all scopes nested quosures correctly", { num <- 10L out <- lazy_frame(x = 1, y = 1) %>% dplyr::mutate_all(~ . + num) %>% sql_build() expect_equal(out$select, sql(x = '`x` + 10', y = '`y` + 10')) }) # ops --------------------------------------------------------------------- test_that("mutate adds new", { out <- tibble(x = 1) %>% tbl_lazy() %>% mutate(y = x + 1, z = y + 1) expect_equal(op_vars(out), c("x", "y", "z")) }) test_that("mutated vars are always named", { mf <- dbplyr::memdb_frame(a = 1) out2 <- mf %>% mutate(1) %>% op_vars() expect_equal(out2, c("a", "1")) }) dbplyr/tests/testthat/test-verb-expand.R0000644000176200001440000000544714004012136020064 0ustar liggesuserstest_that("expand completes all values", { expect_equal( memdb_frame(x = 1:2, y = 1:2) %>% tidyr::expand(x, y) %>% collect(), tibble(x = c(1, 1, 2, 2), y = c(1, 2, 1, 2)) ) expect_snapshot(lazy_frame(x = 1, y = 1) %>% tidyr::expand(x, y)) }) test_that("nesting doesn't expand values", { df <- tibble(x = 1:2, y = 1:2) expect_equal( tidyr::expand(memdb_frame(!!!df), nesting(x, y)) %>% collect(), df ) df_lazy <- lazy_frame(!!!df) expect_snapshot(df_lazy %>% tidyr::expand(nesting(x, y))) }) test_that("expand accepts expressions", { df <- lazy_frame(x = 1) expect_snapshot(tidyr::expand(df, round(x / 2))) expect_snapshot(tidyr::expand(df, nesting(x_half = round(x / 2), x1 = x + 1))) }) test_that("expand respects groups", { df <- tibble( a = c(1L, 1L, 2L), b = c(1L, 2L, 1L), c = c("b", "a", "a") ) expect_equal( memdb_frame(!!!df) %>% group_by(a) %>% tidyr::expand(b, c) %>% collect(), tibble( a = c(1, 1, 1, 1, 2), b = c(1, 1, 2, 2, 1), c = c("b", "a", "b", "a", "a") ) %>% group_by(a) ) df_lazy <- lazy_frame(!!!df) expect_snapshot(df_lazy %>% group_by(a) %>% tidyr::expand(b, c)) }) test_that("NULL inputs", { expect_snapshot(tidyr::expand(lazy_frame(x = 1), x, y = NULL)) }) test_that("expand() errors when expected", { expect_snapshot_error(tidyr::expand(memdb_frame(x = 1))) expect_snapshot_error(tidyr::expand(memdb_frame(x = 1), x = NULL)) }) test_that("expand respect .name_repair", { vars <- suppressMessages( memdb_frame(x = integer(), z = integer()) %>% tidyr::expand(x, z = x, nesting(x), nesting(z), .name_repair = "unique") %>% op_vars() ) expect_equal(vars, c("x...1", "z...2", "x...3", "z...4")) }) # replace_na -------------------------------------------------------------- test_that("replace_na replaces missing values", { expect_equal( memdb_frame(x = c(1, NA), y = c(NA, "b")) %>% tidyr::replace_na(list(x = 0, y = "unknown")) %>% collect(), tibble( x = c(1, 0), y = c("unknown", "b") ) ) expect_snapshot( lazy_frame(x = 1, y = "a") %>% tidyr::replace_na(list(x = 0, y = "unknown")) ) }) test_that("replace_na ignores missing columns", { expect_snapshot(lazy_frame(x = 1) %>% tidyr::replace_na(list(not_there = 0))) }) # complete ---------------------------------------------------------------- test_that("complete completes missing combinations", { df <- tibble( x = 1:2, y = 1:2, z = c("a", "b") ) expect_equal( memdb_frame(!!!df) %>% tidyr::complete(x, y, fill = list(z = "c")) %>% collect(), tibble( x = c(1, 1, 2, 2), y = c(1, 2, 1, 2), z = c("a", "c", "c", "b") ) ) df_lazy <- lazy_frame(!!!df) expect_snapshot(df_lazy %>% tidyr::complete(x, y, fill = list(z = "c"))) }) dbplyr/tests/testthat/test-backend-postgres.R0000644000176200001440000001013514002647450021105 0ustar liggesuserstest_that("custom scalar translated correctly", { local_con(simulate_postgres()) expect_equal(translate_sql(bitwXor(x, 128L)), sql("`x` # 128")) expect_equal(translate_sql(log10(x)), sql("LOG(`x`)")) expect_equal(translate_sql(log(x)), sql("LN(`x`)")) expect_equal(translate_sql(log(x, 2)), sql("LOG(`x`) / LOG(2.0)")) expect_equal(translate_sql(cot(x)), sql("1 / TAN(`x`)")) expect_equal(translate_sql(round(x, digits = 1.1)), sql("ROUND((`x`) :: numeric, 1)")) expect_equal(translate_sql(grepl("exp", x)), sql("(`x`) ~ ('exp')")) expect_equal(translate_sql(grepl("exp", x, TRUE)), sql("(`x`) ~* ('exp')")) expect_equal(translate_sql(substr("test", 2 , 3)), sql("SUBSTR('test', 2, 2)")) }) test_that("custom stringr functions translated correctly", { local_con(simulate_postgres()) expect_equal(translate_sql(str_detect(x, y)), sql("`x` ~ `y`")) expect_equal(translate_sql(str_detect(x, y, negate = TRUE)), sql("!(`x` ~ `y`)")) expect_equal(translate_sql(str_replace(x, y, z)), sql("REGEXP_REPLACE(`x`, `y`, `z`)")) expect_equal(translate_sql(str_replace_all(x, y, z)), sql("REGEXP_REPLACE(`x`, `y`, `z`, 'g')")) expect_equal(translate_sql(str_squish(x)), sql("LTRIM(RTRIM(REGEXP_REPLACE(`x`, '\\s+', ' ', 'g')))")) expect_equal(translate_sql(str_remove(x, y)), sql("REGEXP_REPLACE(`x`, `y`, '')")) expect_equal(translate_sql(str_remove_all(x, y)), sql("REGEXP_REPLACE(`x`, `y`, '', 'g')")) }) test_that("two variable aggregates are translated correctly", { local_con(simulate_postgres()) expect_equal(translate_sql(cor(x, y), window = FALSE), sql("CORR(`x`, `y`)")) expect_equal(translate_sql(cor(x, y), window = TRUE), sql("CORR(`x`, `y`) OVER ()")) }) test_that("pasting translated correctly", { local_con(simulate_postgres()) expect_equal(translate_sql(paste(x, y), window = FALSE), sql("CONCAT_WS(' ', `x`, `y`)")) expect_equal(translate_sql(paste0(x, y), window = FALSE), sql("CONCAT_WS('', `x`, `y`)")) expect_error(translate_sql(paste0(x, collapse = ""), window = FALSE), "`collapse` not supported") }) test_that("postgres mimics two argument log", { local_con(simulate_postgres()) expect_equal(translate_sql(log(x)), sql('LN(`x`)')) expect_equal(translate_sql(log(x, 10)), sql('LOG(`x`) / LOG(10.0)')) expect_equal(translate_sql(log(x, 10L)), sql('LOG(`x`) / LOG(10)')) }) test_that("custom lubridate functions translated correctly", { local_con(simulate_postgres()) expect_equal(translate_sql(yday(x)), sql("EXTRACT(DOY FROM `x`)")) expect_equal(translate_sql(quarter(x)), sql("EXTRACT(QUARTER FROM `x`)")) expect_equal(translate_sql(quarter(x, with_year = TRUE)), sql("(EXTRACT(YEAR FROM `x`) || '.' || EXTRACT(QUARTER FROM `x`))")) expect_error(translate_sql(quarter(x, fiscal_start = 2))) expect_equal(translate_sql(seconds(x)), sql("CAST('`x` seconds' AS INTERVAL)")) expect_equal(translate_sql(minutes(x)), sql("CAST('`x` minutes' AS INTERVAL)")) expect_equal(translate_sql(hours(x)), sql("CAST('`x` hours' AS INTERVAL)")) expect_equal(translate_sql(days(x)), sql("CAST('`x` days' AS INTERVAL)")) expect_equal(translate_sql(weeks(x)), sql("CAST('`x` weeks' AS INTERVAL)")) expect_equal(translate_sql(months(x)), sql("CAST('`x` months' AS INTERVAL)")) expect_equal(translate_sql(years(x)), sql("CAST('`x` years' AS INTERVAL)")) expect_equal(translate_sql(floor_date(x, 'month')), sql("DATE_TRUNC('month', `x`)")) expect_equal(translate_sql(floor_date(x, 'week')), sql("DATE_TRUNC('week', `x`)")) }) test_that("custom SQL translation", { lf <- lazy_frame(x = 1, con = simulate_postgres()) expect_snapshot(left_join(lf, lf, by = "x", na_matches = "na")) }) # live database ----------------------------------------------------------- test_that("can explain", { db <- copy_to_test("postgres", data.frame(x = 1:3)) expect_snapshot(db %>% mutate(y = x + 1) %>% explain()) }) test_that("can overwrite temp tables", { src <- src_test("postgres") copy_to(src, mtcars, "mtcars", overwrite = TRUE) expect_error(copy_to(src, mtcars, "mtcars", overwrite = TRUE), NA) }) dbplyr/tests/testthat/test-translate-sql.R0000644000176200001440000001423214006567571020457 0ustar liggesuserstest_that("dplyr.strict_sql = TRUE prevents auto conversion", { old <- options(dplyr.strict_sql = TRUE) on.exit(options(old)) expect_equal(translate_sql(1 + 2), sql("1.0 + 2.0")) expect_error(translate_sql(blah(x)), "could not find function") }) test_that("namespace calls are translated", { expect_equal(translate_sql(dplyr::n(), window = FALSE), sql("COUNT(*)")) expect_equal(translate_sql(base::ceiling(x)), sql("CEIL(`x`)")) expect_snapshot_error(translate_sql(NOSUCHPACKAGE::foo())) expect_snapshot_error(translate_sql(dbplyr::NOSUCHFUNCTION())) expect_snapshot_error(translate_sql(base::abbreviate(x))) }) test_that("Wrong number of arguments raises error", { expect_error(translate_sql(mean(1, 2, na.rm = TRUE), window = FALSE), "unused argument") }) test_that("between translated to special form (#503)", { out <- translate_sql(between(x, 1, 2)) expect_equal(out, sql("`x` BETWEEN 1.0 AND 2.0")) }) test_that("is.na and is.null are equivalent", { # Needs to be wrapped in parens to ensure correct precedence expect_equal(translate_sql(is.na(x)), sql("((`x`) IS NULL)")) expect_equal(translate_sql(is.null(x)), sql("((`x`) IS NULL)")) expect_equal(translate_sql(x + is.na(x)), sql("`x` + ((`x`) IS NULL)")) expect_equal(translate_sql(!is.na(x)), sql("NOT(((`x`) IS NULL))")) }) test_that("%in% translation parenthesises when needed", { expect_equal(translate_sql(x %in% 1L), sql("`x` IN (1)")) expect_equal(translate_sql(x %in% c(1L)), sql("`x` IN (1)")) expect_equal(translate_sql(x %in% 1:2), sql("`x` IN (1, 2)")) expect_equal(translate_sql(x %in% y), sql("`x` IN `y`")) }) test_that("%in% strips vector names", { expect_equal(translate_sql(x %in% c(a = 1L)), sql("`x` IN (1)")) expect_equal(translate_sql(x %in% !!c(a = 1L)), sql("`x` IN (1)")) }) test_that("%in% with empty vector", { expect_equal(translate_sql(x %in% !!integer()), sql("FALSE")) }) test_that("n_distinct(x) translated to COUNT(distinct, x)", { expect_equal( translate_sql(n_distinct(x), window = FALSE), sql("COUNT(DISTINCT `x`)") ) expect_equal( translate_sql(n_distinct(x), window = TRUE), sql("COUNT(DISTINCT `x`) OVER ()") ) expect_error(translate_sql(n_distinct(x, y), window = FALSE), "unused argument") }) test_that("na_if is translated to NULLIF (#211)", { expect_equal(translate_sql(na_if(x, 0L)), sql("NULLIF(`x`, 0)")) }) test_that("connection affects quoting character", { lf <- lazy_frame(field1 = 1, con = simulate_sqlite()) out <- select(lf, field1) expect_match(sql_render(out), "^SELECT `field1`\nFROM `df`$") }) test_that("magrittr pipe is translated", { expect_identical(translate_sql(1 %>% is.na()), translate_sql(is.na(1))) }) # casts ------------------------------------------------------------------- test_that("casts as expected", { expect_equal(translate_sql(as.integer64(x)), sql("CAST(`x` AS BIGINT)")) expect_equal(translate_sql(as.logical(x)), sql("CAST(`x` AS BOOLEAN)")) expect_equal(translate_sql(as.Date(x)), sql("CAST(`x` AS DATE)")) }) # conditionals ------------------------------------------------------------ test_that("all forms of if translated to case statement", { expected <- sql("CASE WHEN (`x`) THEN (1) WHEN NOT(`x`) THEN (2) END") expect_equal(translate_sql(if (x) 1L else 2L), expected) expect_equal(translate_sql(ifelse(x, 1L, 2L)), expected) expect_equal(translate_sql(if_else(x, 1L, 2L)), expected) }) test_that("if translation adds parens", { expect_equal( translate_sql(if (x) y), sql("CASE WHEN (`x`) THEN (`y`) END") ) expect_equal( translate_sql(if (x) y else z), sql("CASE WHEN (`x`) THEN (`y`) WHEN NOT(`x`) THEN (`z`) END") ) }) test_that("if and ifelse use correctly named arguments",{ exp <- translate_sql(if (x) 1 else 2) expect_equal(translate_sql(ifelse(test = x, yes = 1, no = 2)), exp) expect_equal(translate_sql(if_else(condition = x, true = 1, false = 2)), exp) }) test_that("switch translated to CASE WHEN", { expect_equal( translate_sql(switch(x, a = 1L)), sql("CASE `x` WHEN ('a') THEN (1) END") ) expect_equal( translate_sql(switch(x, a = 1L, 2L)), sql("CASE `x` WHEN ('a') THEN (1) ELSE (2) END") ) }) # numeric ----------------------------------------------------------------- test_that("hypergeometric functions use manual calculation", { expect_equal(translate_sql(cosh(x)), sql("(EXP(`x`) + EXP(-(`x`))) / 2")) expect_equal(translate_sql(sinh(x)), sql("(EXP(`x`) - EXP(-(`x`))) / 2")) expect_equal(translate_sql(tanh(x)), sql("(EXP(2 * (`x`)) - 1) / (EXP(2 * (`x`)) + 1)")) expect_equal(translate_sql(coth(x)), sql("(EXP(2 * (`x`)) + 1) / (EXP(2 * (`x`)) - 1)")) }) test_that("pmin and max use GREATEST and LEAST", { expect_warning(translate_sql(pmin(x, y)), "always removed") expect_equal(translate_sql(pmin(x, y, z)), sql("LEAST(`x`, `y`, `z`)")) expect_equal(translate_sql(pmax(x, y, na.rm = TRUE)), sql("GREATEST(`x`, `y`)")) }) test_that("round uses integer digits", { expect_equal(translate_sql(round(10.1)), sql("ROUND(10.1, 0)")) expect_equal(translate_sql(round(10.1, digits = 1)), sql("ROUND(10.1, 1)")) }) # string functions -------------------------------------------------------- test_that("paste() translated to CONCAT_WS", { expect_equal(translate_sql(paste0(x, y)), sql("CONCAT_WS('', `x`, `y`)")) expect_equal(translate_sql(paste(x, y)), sql("CONCAT_WS(' ', `x`, `y`)")) expect_equal(translate_sql(paste(x, y, sep = ",")), sql("CONCAT_WS(',', `x`, `y`)")) }) # stringr ------------------------------------------- test_that("str_length() translates correctly ", { expect_equal(translate_sql(str_length(x)), sql("LENGTH(`x`)")) }) test_that("lower/upper translates correctly ", { expect_equal(translate_sql(str_to_upper(x)), sql("UPPER(`x`)")) expect_equal(translate_sql(str_to_lower(x)), sql("LOWER(`x`)")) }) test_that("str_trim() translates correctly ", { expect_equal( translate_sql(str_trim(x, "both")), sql("LTRIM(RTRIM(`x`))") ) }) # subsetting -------------------------------------------------------------- test_that("[ treated as if it is logical subsetting", { expect_equal(translate_sql(y[x == 0L]), sql("CASE WHEN (`x` = 0) THEN (`y`) END")) }) dbplyr/tests/testthat/test-verb-group_by.R0000644000176200001440000000526614031447261020445 0ustar liggesuserstest_that("group_by with .add = TRUE adds groups", { mf <- memdb_frame(x = 1:3, y = 1:3) gf1 <- mf %>% group_by(x, y) gf2 <- mf %>% group_by(x) %>% group_by(y, .add = TRUE) expect_equal(group_vars(gf1), c("x", "y")) expect_equal(group_vars(gf2), c("x", "y")) }) test_that("warns about add argument ", { mf <- memdb_frame(x = 1:3, y = 1:3) expect_warning( gf <- mf %>% group_by(x) %>% group_by(y, add = TRUE), "deprecated" ) expect_equal(group_vars(gf), c("x", "y")) }) test_that("collect, collapse and compute preserve grouping", { g <- memdb_frame(x = 1:3, y = 1:3) %>% group_by(x, y) expect_equal(group_vars(compute(g)), c("x", "y")) expect_equal(group_vars(collapse(g)), c("x", "y")) expect_equal(group_vars(collect(g)), c("x", "y")) }) test_that("joins preserve grouping", { g <- memdb_frame(x = 1:3, y = 1:3) %>% group_by(x) expect_equal(group_vars(inner_join(g, g, by = c("x", "y"))), "x") expect_equal(group_vars(left_join(g, g, by = c("x", "y"))), "x") expect_equal(group_vars(semi_join(g, g, by = c("x", "y"))), "x") expect_equal(group_vars(anti_join(g, g, by = c("x", "y"))), "x") }) test_that("group_by can perform mutate", { mf <- memdb_frame(x = 3:1, y = 1:3) out <- mf %>% group_by(z = x + y) %>% summarise(n = n()) %>% collect() expect_equal(out, tibble(z = 4L, n = 3L)) }) test_that("group_by handles empty dots", { lf <- lazy_frame(x = 1) %>% group_by(x) expect_equal(lf %>% group_by() %>% group_vars(), character()) expect_equal(lf %>% group_by(.add = TRUE) %>% group_vars(), c("x")) }) # sql_build --------------------------------------------------------------- test_that("ungroup drops PARTITION BY", { out <- lazy_frame(x = 1) %>% group_by(x) %>% ungroup() %>% mutate(x = rank(x)) %>% sql_build() expect_equal(out$select, sql(x = 'RANK() OVER (ORDER BY `x`)')) }) # ops --------------------------------------------------------------------- test_that("group_by overrides existing groups", { df <- tibble(g1 = 1, g2 = 2, x = 3) %>% tbl_lazy() out1 <- df %>% group_by(g1) expect_equal(op_grps(out1), "g1") out2 <- out1 %>% group_by(g2) expect_equal(op_grps(out2), "g2") }) test_that("group_by increases grouping if add = TRUE", { df <- tibble(g1 = 1, g2 = 2, x = 3) %>% tbl_lazy() out <- df %>% group_by(g1) %>% group_by(g2, .add = TRUE) expect_equal(op_grps(out), c("g1", "g2")) }) test_that("ungroup drops all groups", { out1 <- lazy_frame(g1 = 1, g2 = 2) %>% group_by(g1, g2) %>% ungroup() out2 <- lazy_frame(g1 = 1, g2 = 2) %>% group_by(g1, g2) %>% ungroup() %>% rename(g3 = g1) expect_equal(op_grps(out1), character()) expect_equal(op_grps(out2), character()) }) dbplyr/tests/testthat/test-verb-pull.R0000644000176200001440000000120114004012136017541 0ustar liggesuserstest_that("can extract default, by name, or positive/negative position", { x <- 1:10 y <- runif(10) mf <- memdb_frame(x = x, y = y) expect_equal(pull(mf), y) expect_equal(pull(mf, x), x) expect_equal(pull(mf, 1L), x) expect_equal(pull(mf, -1), y) }) test_that("extracts correct column from grouped tbl", { mf <- memdb_frame(id = "a", value = 42) gf <- mf %>% group_by(id) expect_equal(pull(mf, value), 42) }) test_that("doesn't unnecessarily select", { mf <- memdb_frame(x = c(3, 1, 2)) # no warning about select after arrange expect_warning(out <- mf %>% arrange(x) %>% pull(), NA) expect_equal(out, 1:3) }) dbplyr/tests/testthat/test-backend-oracle.R0000644000176200001440000000174014015731174020507 0ustar liggesuserstest_that("custom scalar functions translated correctly", { local_con(simulate_oracle()) expect_equal(translate_sql(as.character(x)), sql("CAST(`x` AS VARCHAR2(255))")) expect_equal(translate_sql(as.integer64(x)), sql("CAST(`x` AS NUMBER(19))")) expect_equal(translate_sql(as.double(x)), sql("CAST(`x` AS NUMBER)")) }) test_that("paste and paste0 translate correctly", { local_con(simulate_oracle()) expect_equal(translate_sql(paste(x, y)), sql("`x` || ' ' || `y`")) expect_equal(translate_sql(paste0(x, y)), sql("`x` || `y`")) }) test_that("queries translate correctly", { mf <- lazy_frame(x = 1, con = simulate_oracle()) expect_snapshot(mf %>% head()) }) test_that("generates custom sql", { con <- simulate_oracle() expect_snapshot(sql_table_analyze(con, in_schema("schema", "tbl"))) expect_snapshot(sql_query_explain(con, sql("SELECT * FROM foo"))) lf <- lazy_frame(x = 1, con = con) expect_snapshot(left_join(lf, lf, by = "x", na_matches = "na")) }) dbplyr/tests/testthat/test-schema.R0000644000176200001440000000225014002647450017111 0ustar liggesuserstest_that("can construct and print", { expect_snapshot(in_schema("schema", "table")) }) test_that("escaped as needed", { con <- simulate_dbi() expect_equal(as.sql(in_schema("s", "t"), con), ident_q("`s`.`t`")) expect_equal(as.sql(in_schema(sql("s"), sql("t")), con), ident_q("s.t")) }) test_that("can copy and collect with schema or Id", { con <- sqlite_con_with_aux() on.exit(dbDisconnect(con)) df <- tibble(x = 1:10) db <- copy_to(con, df, in_schema("aux", "db1"), temporary = FALSE) expect_equal(collect(db), df) expect_equal(collect(filter(db, x < 2)), df[1, ]) db <- copy_to(con, df, Id(schema = "aux", table = "db2"), temporary = FALSE) expect_equal(collect(db), df) expect_equal(collect(filter(db, x < 2)), df[1, ]) }) test_that("quoted identifier correctly escaped", { con <- simulate_dbi() x2 <- ident_q('"x"') expect_equal(as.sql(x2), x2) expect_equal(escape(x2, con = con), sql('"x"')) expect_equal(sql_vector(ident_q(), collapse = NULL, con = con), sql()) expect_equal(sql_vector(ident_q(), parens = FALSE, collapse = "", con = con), sql("")) expect_equal(sql_vector(ident_q(), parens = TRUE, collapse = "", con = con), sql("()")) }) dbplyr/tests/testthat/test-db-sql.R0000644000176200001440000000064614002647450017042 0ustar liggesuserstest_that("2nd edition uses sql methods", { local_methods( db_analyze.Test = function(con, ...) stop("db_method") ) con <- structure(list(), class = c("Test", "DBIConnection")) expect_error(dbplyr_analyze(con), "db_method") local_methods( dbplyr_edition.Test = function(con) 2, sql_table_analyze.Test = function(con, ...) stop("sql_method") ) expect_error(dbplyr_analyze(con), "sql_method") }) dbplyr/tests/testthat/test-verb-count.R0000644000176200001440000000122614006531012017725 0ustar liggesuserstest_that("generates expected SQL for common situations", { db <- lazy_frame(g = 1, x = 2) expect_snapshot(db %>% count(g)) expect_snapshot(db %>% count(g, wt = x)) expect_snapshot(db %>% count(g, sort = TRUE)) }) test_that("preserves group of input", { db <- lazy_frame(g = 1, x = 2) expect_equal(db %>% count(g) %>% group_vars(), character()) expect_equal(db %>% group_by(g) %>% count() %>% group_vars(), "g") expect_equal(db %>% group_by(g, x) %>% count() %>% group_vars(), c("g", "x")) }) test_that("complains about bad names", { expect_snapshot(error = TRUE, { db <- lazy_frame(g = 1, x = 2) db %>% count(g, name = "g") }) }) dbplyr/tests/testthat/test-verb-pivot-wider.R0000644000176200001440000001247014004012136021050 0ustar liggesusersspec <- tibble( .name = c("x", "y"), .value = "val", key = c("x", "y") ) spec1 <- tibble(.name = "x", .value = "val", key = "x") test_that("can pivot all cols to wide", { expect_equal( memdb_frame(key = c("x", "y", "z"), val = 1:3) %>% tidyr::pivot_wider(names_from = key, values_from = val) %>% collect(), tibble(x = 1, y = 2, z = 3) ) spec <- tibble( .name = c("x", "y", "z"), .value = "val", key = c("x", "y", "z") ) expect_snapshot( lazy_frame(key = c("x", "y", "z"), val = 1:3) %>% dbplyr_pivot_wider_spec(spec) ) }) test_that("non-pivoted cols are preserved", { df <- lazy_frame(a = 1, key = c("x", "y"), val = 1:2) expect_equal( dbplyr_pivot_wider_spec(df, spec) %>% op_vars(), c("a", "x", "y") ) }) test_that("implicit missings turn into explicit missings", { df <- memdb_frame(a = 1:2, key = c("x", "y"), val = 1:2) expect_equal( memdb_frame(a = 1:2, key = c("x", "y"), val = 1:2) %>% tidyr::pivot_wider(names_from = key, values_from = val) %>% collect(), tibble(a = 1:2, x = c(1, NA), y = c(NA, 2)) ) expect_snapshot( lazy_frame(a = 1:2, key = c("x", "y"), val = 1:2) %>% dbplyr_pivot_wider_spec(spec) ) }) test_that("error when overwriting existing column", { df <- memdb_frame( a = c(1, 1), key = c("a", "b"), val = c(1, 2) ) expect_snapshot_error( tidyr::pivot_wider(df, names_from = key, values_from = val) ) }) test_that("grouping is preserved", { df <- lazy_frame(a = 1, key = "x", val = 2) expect_equal( df %>% dplyr::group_by(a) %>% dbplyr_pivot_wider_spec(spec1) %>% group_vars(), "a" ) }) # https://github.com/tidyverse/tidyr/issues/804 test_that("column with `...j` name can be used as `names_from`", { df <- memdb_frame(...8 = c("x", "y", "z"), val = 1:3) pv <- tidyr::pivot_wider(df, names_from = ...8, values_from = val) %>% collect() expect_named(pv, c("x", "y", "z")) }) # column names ------------------------------------------------------------- test_that("dbplyr_build_wider_spec can handle multiple columns", { df <- memdb_frame( x = c("X", "Y"), y = 1:2, a = 1:2, b = 1:2 ) expect_equal( dbplyr_build_wider_spec(df, x:y, a:b), tibble::tribble( ~.name, ~.value, ~x, ~y, "a_X_1", "a", "X", 1L, "a_Y_2", "a", "Y", 2L, "b_X_1", "b", "X", 1L, "b_Y_2", "b", "Y", 2L ) ) }) # keys --------------------------------------------------------- test_that("can override default keys", { df <- tibble::tribble( ~row, ~name, ~var, ~value, 1, "Sam", "age", 10, 2, "Sam", "height", 1.5, 3, "Bob", "age", 20, ) df_db <- memdb_frame(!!!df) expect_equal( df_db %>% tidyr::pivot_wider(id_cols = name, names_from = var, values_from = value) %>% collect(), tibble::tribble( ~name, ~age, ~height, "Bob", 20, NA, "Sam", 10, 1.5 ) ) }) # non-unqiue keys --------------------------------------------------------- test_that("values_fn can be a single function", { df <- lazy_frame(a = c(1, 1, 2), key = c("x", "x", "x"), val = c(1, 10, 100)) expect_snapshot(dbplyr_pivot_wider_spec(df, spec1, values_fn = sum)) }) test_that("values_fn cannot be NULL", { df <- lazy_frame(a = 1, key = "x", val = 1) expect_snapshot_error(dbplyr_pivot_wider_spec(df, spec1, values_fn = NULL)) }) # can fill missing cells -------------------------------------------------- test_that("can fill in missing cells", { df <- memdb_frame(g = c(1, 2), name = c("x", "y"), value = c(1, 2)) df_lazy <- lazy_frame(g = c(1, 2), name = c("x", "y"), value = c(1, 2)) expect_equal(tidyr::pivot_wider(df) %>% pull(x), c(1, NA)) expect_equal(tidyr::pivot_wider(df, values_fill = 0) %>% pull(x), c(1, 0)) expect_snapshot(dbplyr_pivot_wider_spec(df_lazy, spec, values_fill = 0)) expect_equal( tidyr::pivot_wider(df, values_fill = list(value = 0)) %>% pull(x), c(1, 0) ) expect_snapshot( dbplyr_pivot_wider_spec( df_lazy, spec, values_fill = list(value = 0) ) ) }) test_that("values_fill only affects missing cells", { df <- memdb_frame(g = c(1, 2), name = c("x", "y"), value = c(1, NA)) out <- tidyr::pivot_wider(df, values_fill = 0) %>% collect() expect_equal(out$y, c(0, NA)) }) # multiple values ---------------------------------------------------------- test_that("can pivot from multiple measure cols", { df <- memdb_frame(row = 1, var = c("x", "y"), a = 1:2, b = 3:4) pv <- tidyr::pivot_wider(df, names_from = var, values_from = c(a, b)) %>% collect() expect_named(pv, c("row", "a_x", "a_y", "b_x", "b_y")) expect_equal(pv$a_x, 1) expect_equal(pv$b_y, 4) }) test_that("column order in output matches spec", { df <- tibble::tribble( ~hw, ~name, ~mark, ~pr, "hw1", "anna", 95, "ok", "hw2", "anna", 70, "meh", ) # deliberately create weird order sp <- tibble::tribble( ~hw, ~.value, ~.name, "hw1", "mark", "hw1_mark", "hw1", "pr", "hw1_pr", "hw2", "pr", "hw2_pr", "hw2", "mark", "hw2_mark", ) pv <- dbplyr_pivot_wider_spec(lazy_frame(!!!df), sp) expect_equal(pv %>% op_vars(), c("name", sp$.name)) }) test_that("cannot pivot lazy frames", { expect_snapshot_error(tidyr::pivot_wider(lazy_frame(name = "x", value = 1))) }) dbplyr/tests/testthat/test-query-set-op.R0000644000176200001440000000100214002647450020215 0ustar liggesuserstest_that("print method doesn't change unexpectedly", { lf1 <- lazy_frame(x = 1, y = 2) lf2 <- lazy_frame(x = 1, z = 2) expect_snapshot(sql_build(union(lf1, lf2))) }) test_that("generated sql doesn't change unexpectedly", { lf <- lazy_frame(x = 1, y = 2) expect_snapshot(union(lf, lf)) expect_snapshot(setdiff(lf, lf)) expect_snapshot(intersect(lf, lf)) expect_snapshot(union(lf, lf, all = TRUE)) expect_snapshot(setdiff(lf, lf, all = TRUE)) expect_snapshot(intersect(lf, lf, all = TRUE)) }) dbplyr/tests/testthat/test-remote.R0000644000176200001440000000100214002647450017136 0ustar liggesuserstest_that("remote_name returns null for computed tables", { mf <- memdb_frame(x = 5, .name = "refxiudlph") expect_equal(remote_name(mf), ident("refxiudlph")) mf2 <- mf %>% filter(x == 3) expect_equal(remote_name(mf2), NULL) }) test_that("can retrieve query, src and con metadata", { mf <- memdb_frame(x = 5) expect_s4_class(remote_con(mf), "DBIConnection") expect_s3_class(remote_src(mf), "src_sql") expect_s3_class(remote_query(mf), "sql") expect_type(remote_query_plan(mf), "character") }) dbplyr/tests/testthat/test-tbl-sql.R0000644000176200001440000000531014002647450017227 0ustar liggesuserstest_that("tbl_sql() works with string argument", { name <- unclass(unique_table_name()) df <- memdb_frame(a = 1, .name = name) expect_equal(collect(tbl_sql("sqlite", df$src, name)), collect(df)) }) test_that("same_src distinguishes srcs", { con1 <- DBI::dbConnect(RSQLite::SQLite(), ":memory:", create = TRUE) con2 <- DBI::dbConnect(RSQLite::SQLite(), ":memory:", create = TRUE) on.exit({dbDisconnect(con1); dbDisconnect(con2)}, add = TRUE) db1 <- copy_to(con1, iris[1:3, ], 'data1', temporary = FALSE) db2 <- copy_to(con2, iris[1:3, ], 'data2', temporary = FALSE) expect_true(same_src(db1, db1)) expect_false(same_src(db1, db2)) expect_false(same_src(db1, mtcars)) }) # tbl --------------------------------------------------------------------- test_that("can generate sql tbls with raw sql", { mf1 <- memdb_frame(x = 1:3, y = 3:1) mf2 <- tbl(mf1$src, build_sql("SELECT * FROM ", mf1$ops$x, con = simulate_dbi())) expect_equal(collect(mf1), collect(mf2)) }) test_that("can refer to default schema explicitly", { con <- sqlite_con_with_aux() on.exit(DBI::dbDisconnect(con)) DBI::dbExecute(con, "CREATE TABLE t1 (x)") expect_equal(as.character(tbl_vars(tbl(con, "t1"))), "x") expect_equal(as.character(tbl_vars(tbl(con, in_schema("main", "t1")))), "x") }) test_that("can distinguish 'schema.table' from 'schema'.'table'", { con <- sqlite_con_with_aux() on.exit(DBI::dbDisconnect(con)) DBI::dbExecute(con, "CREATE TABLE aux.t1 (x, y, z)") DBI::dbExecute(con, "CREATE TABLE 'aux.t1' (a, b, c)") expect_equal(as.character(tbl_vars(tbl(con, in_schema("aux", "t1")))), c("x", "y", "z")) expect_equal(as.character(tbl_vars(tbl(con, ident("aux.t1")))), c("a", "b", "c")) }) # n_groups ---------------------------------------------------------------- test_that("check basic group size implementation", { db <- memdb_frame(x = rep(1:3, each = 10), y = rep(1:6, each = 5)) expect_equal(n_groups(db), 1L) expect_equal(group_size(db), 30) gb <- group_by(db, x) expect_equal(n_groups(gb), 3L) expect_equal(group_size(gb), rep(10, 3)) }) # tbl_sum ------------------------------------------------------------------- test_that("ungrouped output", { mf <- memdb_frame(x = 1:5, y = 1:5, .name = "tbl_sum_test") out1 <- tbl_sum(mf) expect_named(out1, c("Source", "Database")) expect_equal(out1[["Source"]], "table [?? x 2]") expect_match(out1[["Database"]], "sqlite (.*) \\[:memory:\\]") out2 <- tbl_sum(mf %>% group_by(x, y)) expect_named(out2, c("Source", "Database", "Groups")) expect_equal(out2[["Groups"]], c("x, y")) out3 <- tbl_sum(mf %>% arrange(x)) expect_named(out3, c("Source", "Database", "Ordered by")) expect_equal(out3[["Ordered by"]], c("x")) }) dbplyr/tests/testthat/test-verb-arrange.R0000644000176200001440000000663514002647450020237 0ustar liggesuserstest_that("two arranges equivalent to one", { mf <- memdb_frame(x = c(2, 2, 1), y = c(1, -1, 1)) mf1 <- mf %>% arrange(x, y) mf2 <- mf %>% arrange(y) %>% arrange(x) expect_warning(expect_equal_tbl(mf1, mf2)) }) # sql_render -------------------------------------------------------------- test_that("quoting for rendering ordered grouped table", { db <- memdb_frame(x = 1, y = 2, .name = "test-verb-arrange") out <- db %>% group_by(x) %>% arrange(y) %>% ungroup() expect_snapshot(sql_render(out)) expect_equal(collect(out), tibble(x = 1, y = 2)) }) test_that("arrange renders correctly (#373)", { expect_snapshot({ "# arrange renders correctly" lf <- lazy_frame(a = 1:3, b = 3:1) "basic" lf %>% arrange(a) "double arrange" lf %>% arrange(a) %>% arrange(b) "remove ordered by" lf %>% arrange(a) %>% select(-a) lf %>% arrange(a) %>% select(-a) %>% arrange(b) "un-arrange" lf %>% arrange(a) %>% arrange() lf %>% arrange(a) %>% select(-a) %>% arrange() "use order" lf %>% arrange(a) %>% select(-a) %>% mutate(c = lag(b)) }) }) test_that("arrange renders correctly for single-table verbs (#373)", { expect_snapshot({ lf <- lazy_frame(a = 1:3, b = 3:1) "head" lf %>% head(1) %>% arrange(a) lf %>% arrange(a) %>% head(1) lf %>% arrange(a) %>% head(1) %>% arrange(b) "mutate" lf %>% mutate(a = b) %>% arrange(a) "complex mutate" lf %>% arrange(a) %>% mutate(a = b) %>% arrange(a) lf %>% arrange(a) %>% mutate(a = 1) %>% arrange(b) lf %>% mutate(a = -a) %>% arrange(a) %>% mutate(a = -a) }) }) test_that("can combine arrange with dual table verbs", { expect_snapshot({ lf <- lazy_frame(a = 1:3, b = 3:1) rf <- lazy_frame(a = 1:3, c = 4:6) "warn if arrange before join" lf %>% arrange(a) %>% left_join(rf) lf %>% arrange(a) %>% semi_join(rf) lf %>% arrange(a) %>% union(rf) "can arrange after join" lf %>% left_join(rf) %>% arrange(a) lf %>% semi_join(rf) %>% arrange(a) lf %>% union(rf) %>% arrange(a) }) }) # sql_build --------------------------------------------------------------- test_that("arrange generates order_by", { out <- lazy_frame(x = 1, y = 1) %>% arrange(x) %>% sql_build() expect_equal(out$order_by, sql('`x`')) }) test_that("arrange converts desc", { out <- lazy_frame(x = 1, y = 1) %>% arrange(desc(x)) %>% sql_build() expect_equal(out$order_by, sql('`x` DESC')) }) test_that("grouped arrange doesn't order by groups", { out <- lazy_frame(x = 1, y = 1) %>% group_by(x) %>% arrange(y) %>% sql_build() expect_equal(out$order_by, sql('`y`')) }) test_that("grouped arrange order by groups when .by_group is set to TRUE", { lf <- lazy_frame(x = 1, y = 1, con = simulate_dbi()) out <- lf %>% group_by(x) %>% arrange(y, .by_group = TRUE) %>% sql_build() expect_equal(out$order_by, sql(c('`x`','`y`'))) }) # ops --------------------------------------------------------------------- test_that("arranges captures DESC", { out <- lazy_frame(x = 1:3, y = 3:1) %>% arrange(desc(x)) sort <- lapply(op_sort(out), get_expr) expect_equal(sort, list(quote(desc(x)))) }) test_that("multiple arranges combine", { out <- lazy_frame(x = 1:3, y = 3:1) %>% arrange(x) %>% arrange(y) out <- arrange(arrange(lazy_frame(x = 1:3, y = 3:1), x), y) sort <- lapply(op_sort(out), get_expr) expect_equal(sort, list(quote(x), quote(y))) }) dbplyr/tests/testthat/test-backend-teradata.R0000644000176200001440000000464414015731174021035 0ustar liggesuserstest_that("custom scalar translated correctly", { local_con(simulate_teradata()) expect_equal(translate_sql(x != y), sql("`x` <> `y`")) expect_equal(translate_sql(as.numeric(x)), sql("CAST(`x` AS NUMERIC)")) expect_equal(translate_sql(as.double(x)), sql("CAST(`x` AS NUMERIC)")) expect_equal(translate_sql(as.character(x)), sql("CAST(`x` AS VARCHAR(MAX))")) expect_equal(translate_sql(log(x)), sql("LN(`x`)")) expect_equal(translate_sql(cot(x)), sql("1 / TAN(`x`)")) expect_equal(translate_sql(nchar(x)), sql("CHARACTER_LENGTH(`x`)")) expect_equal(translate_sql(ceil(x)), sql("CEILING(`x`)")) expect_equal(translate_sql(ceiling(x)), sql("CEILING(`x`)")) expect_equal(translate_sql(atan2(x, y)), sql("ATAN2(`y`, `x`)")) expect_equal(translate_sql(substr(x, 1, 2)), sql("SUBSTR(`x`, 1.0, 2.0)")) expect_error(translate_sql(paste(x)), sql("not supported")) }) test_that("custom bitwise operations translated correctly", { local_con(simulate_teradata()) expect_equal(translate_sql(bitwNot(x)), sql("BITNOT(`x`)")) expect_equal(translate_sql(bitwAnd(x, 128L)), sql("BITAND(`x`, 128)")) expect_equal(translate_sql(bitwOr(x, 128L)), sql("BITOR(`x`, 128)")) expect_equal(translate_sql(bitwXor(x, 128L)), sql("BITXOR(`x`, 128)")) expect_equal(translate_sql(bitwShiftL(x, 2L)), sql("SHIFTLEFT(`x`, 2)")) expect_equal(translate_sql(bitwShiftR(x, 2L)), sql("SHIFTRIGHT(`x`, 2)")) }) test_that("custom aggregators translated correctly", { local_con(simulate_teradata()) expect_equal(translate_sql(var(x, na.rm = TRUE), window = FALSE), sql("VAR_SAMP(`x`)")) expect_error(translate_sql(cor(x), window = FALSE), "not available") expect_error(translate_sql(cov(x), window = FALSE), "not available") }) test_that("custom window functions translated correctly", { local_con(simulate_teradata()) expect_equal(translate_sql(var(x, na.rm = TRUE)), sql("VAR_SAMP(`x`) OVER ()")) expect_error(translate_sql(cor(x)), "not supported") expect_error(translate_sql(cov(x)), "not supported") }) test_that("generates custom sql", { con <- simulate_teradata() expect_snapshot(sql_table_analyze(con, in_schema("schema", "tbl"))) }) # verb translation -------------------------------------------------------- test_that("head translated to TOP", { mf <- lazy_frame(x = 1, con = simulate_teradata()) expect_snapshot(mf %>% head() %>% sql_render()) }) dbplyr/tests/testthat/test-translate-sql-helpers.R0000644000176200001440000000226514002647450022111 0ustar liggesuserstest_that("aggregation functions warn if na.rm = FALSE", { local_con(simulate_dbi()) sql_mean <- sql_aggregate("MEAN") expect_warning(sql_mean("x"), "Missing values") expect_warning(sql_mean("x", na.rm = TRUE), NA) }) test_that("missing window functions create a warning", { local_con(simulate_dbi()) sim_scalar <- sql_translator() sim_agg <- sql_translator(`+` = sql_infix("+")) sim_win <- sql_translator() expect_warning( sql_variant(sim_scalar, sim_agg, sim_win), "Translator is missing" ) }) test_that("missing aggregate functions filled in", { local_con(simulate_dbi()) sim_scalar <- sql_translator() sim_agg <- sql_translator() sim_win <- sql_translator(mean = function() {}) trans <- sql_variant(sim_scalar, sim_agg, sim_win) expect_error(trans$aggregate$mean(), "only available in a window") }) test_that("output of print method for sql_variant is correct", { sim_trans <- sql_translator(`+` = sql_infix("+")) expect_snapshot(sql_variant(sim_trans, sim_trans, sim_trans)) }) test_that("win_rank() is accepted by the sql_translator", { expect_snapshot( sql_variant( sql_translator( test = win_rank("test") ) ) ) }) dbplyr/tests/testthat/test-backend-sqlite.R0000644000176200001440000000637014004012136020533 0ustar liggesuserstest_that("logicals translated to integers", { expect_equal(escape(FALSE, con = simulate_sqlite()), sql("0")) expect_equal(escape(TRUE, con = simulate_sqlite()), sql("1")) expect_equal(escape(NA, con = simulate_sqlite()), sql("NULL")) }) test_that("vectorised translations", { local_con(simulate_sqlite()) expect_equal(translate_sql(paste(x, y)), sql("`x` || ' ' || `y`")) expect_equal(translate_sql(paste0(x, y)), sql("`x` || `y`")) }) test_that("pmin and max become MIN and MAX", { local_con(simulate_sqlite()) expect_equal(translate_sql(pmin(x, y, na.rm = TRUE)), sql('MIN(`x`, `y`)')) expect_equal(translate_sql(pmax(x, y, na.rm = TRUE)), sql('MAX(`x`, `y`)')) }) test_that("sqlite mimics two argument log", { local_con(simulate_sqlite()) expect_equal(translate_sql(log(x)), sql('LOG(`x`)')) expect_equal(translate_sql(log(x, 10)), sql('LOG(`x`) / LOG(10.0)')) }) test_that("date-time", { local_con(simulate_sqlite()) expect_equal(translate_sql(today()), sql("DATE('now')")) expect_equal(translate_sql(now()), sql("DATETIME('now')")) }) test_that("custom aggregates translated", { local_con(simulate_sqlite()) expect_equal(translate_sql(median(x, na.rm = TRUE), window = FALSE), sql('MEDIAN(`x`)')) expect_equal(translate_sql(sd(x, na.rm = TRUE), window = FALSE), sql('STDEV(`x`)')) }) test_that("custom SQL translation", { lf <- lazy_frame(x = 1, con = simulate_sqlite()) expect_snapshot(left_join(lf, lf, by = "x", na_matches = "na")) }) test_that("full and right join", { df1 <- lazy_frame( x = 1:3, y = c("x", "y", "z"), con = simulate_sqlite() ) df2 <- lazy_frame( x = c(1, 3, 5), y = c("a", "b", "c"), z = 11:13, con = simulate_sqlite() ) expect_snapshot(full_join(df1, df2, by = "x")) expect_snapshot(right_join(df2, df1, by = "x")) }) # live database ----------------------------------------------------------- test_that("as.numeric()/as.double() get custom translation", { mf <- dbplyr::memdb_frame(x = 1L) out <- mf %>% mutate(x1 = as.numeric(x), x2 = as.double(x)) %>% collect() expect_type(out$x1, "double") expect_type(out$x2, "double") }) test_that("date extraction agrees with R", { db <- memdb_frame(x = "2000-01-02 03:40:50.5") out <- db %>% transmute( year = year(x), month = month(x), day = day(x), hour = hour(x), minute = minute(x), second = second(x), yday = yday(x), ) %>% collect() %>% as.list() expect_equal(out, list( year = 2000, month = 1, day = 2, hour = 3, minute = 40, second = 50.5, yday = 2 )) }) test_that("can explain a query", { db <- copy_to_test("sqlite", data.frame(x = 1:5), indexes = list("x")) expect_snapshot(db %>% filter(x > 2) %>% explain()) }) test_that("full and right join work", { df1 <- memdb_frame(x = 1:3, y = c("x", "y", "z")) df2 <- memdb_frame(x = c(1, 3, 5), y = c("a", "b", "c"), z = 11:13) expect_equal( collect(full_join(df1, df2, by = "x")), tibble( x = c(1, 2, 3, 5), y.x = c("x", "y", "z", NA), y.y = c("a", NA, "b", "c"), z = c(11, NA, 12, 13) ) ) expect_equal( collect(right_join(df2, df1, by = "x")), tibble( x = c(1, 2, 3), y.x = c("a", NA, "b"), z = c(11, NA, 12), y.y = c("x", "y", "z") ) ) }) dbplyr/tests/testthat/test-backend-mssql.R0000644000176200001440000001754014015731174020406 0ustar liggesusers# function translation ---------------------------------------------------- test_that("custom scalar translated correctly", { local_con(simulate_mssql()) expect_equal(translate_sql(as.logical(x)), sql("TRY_CAST(`x` AS BIT)")) expect_equal(translate_sql(as.numeric(x)), sql("TRY_CAST(`x` AS FLOAT)")) expect_equal(translate_sql(as.integer(x)), sql("TRY_CAST(TRY_CAST(`x` AS NUMERIC) AS INT)")) expect_equal(translate_sql(as.integer64(x)), sql("TRY_CAST(TRY_CAST(`x` AS NUMERIC(38, 0)) AS BIGINT)")) expect_equal(translate_sql(as.double(x)), sql("TRY_CAST(`x` AS FLOAT)")) expect_equal(translate_sql(as.character(x)), sql("TRY_CAST(`x` AS VARCHAR(MAX))")) expect_equal(translate_sql(log(x)), sql("LOG(`x`)")) expect_equal(translate_sql(nchar(x)), sql("LEN(`x`)")) expect_equal(translate_sql(atan2(x)), sql("ATN2(`x`)")) expect_equal(translate_sql(ceiling(x)), sql("CEILING(`x`)")) expect_equal(translate_sql(ceil(x)), sql("CEILING(`x`)")) expect_equal(translate_sql(substr(x, 1, 2)), sql("SUBSTRING(`x`, 1, 2)")) expect_equal(translate_sql(trimws(x)), sql("LTRIM(RTRIM(`x`))")) expect_equal(translate_sql(paste(x, y)), sql("`x` + ' ' + `y`")) expect_error(translate_sql(bitwShiftL(x, 2L)), sql("not available")) expect_error(translate_sql(bitwShiftR(x, 2L)), sql("not available")) }) test_that("contents of [ have bool context", { local_con(simulate_mssql()) local_context(list(clause = "SELECT")) expect_equal(translate_sql(x[x > y]), sql("CASE WHEN (`x` > `y`) THEN (`x`) END")) }) test_that("custom stringr functions translated correctly", { local_con(simulate_mssql()) expect_equal(translate_sql(str_length(x)), sql("LEN(`x`)")) }) test_that("custom aggregators translated correctly", { local_con(simulate_mssql()) expect_equal(translate_sql(sd(x, na.rm = TRUE), window = FALSE), sql("STDEV(`x`)")) expect_equal(translate_sql(var(x, na.rm = TRUE), window = FALSE), sql("VAR(`x`)")) expect_error(translate_sql(cor(x), window = FALSE), "not available") expect_error(translate_sql(cov(x), window = FALSE), "not available") expect_equal(translate_sql(str_flatten(x), window = FALSE), sql("STRING_AGG(`x`, '')")) }) test_that("custom window functions translated correctly", { local_con(simulate_mssql()) expect_equal(translate_sql(sd(x, na.rm = TRUE)), sql("STDEV(`x`) OVER ()")) expect_equal(translate_sql(var(x, na.rm = TRUE)), sql("VAR(`x`) OVER ()")) expect_error(translate_sql(cor(x)), "not supported") expect_error(translate_sql(cov(x)), "not supported") expect_equal(translate_sql(str_flatten(x)), sql("STRING_AGG(`x`, '') OVER ()")) }) test_that("custom lubridate functions translated correctly", { local_con(simulate_mssql()) expect_equal(translate_sql(as_date(x)), sql("TRY_CAST(`x` AS DATE)")) expect_equal(translate_sql(as_datetime(x)), sql("TRY_CAST(`x` AS DATETIME2)")) expect_equal(translate_sql(today()), sql("CAST(SYSDATETIME() AS DATE)")) expect_equal(translate_sql(year(x)), sql("DATEPART(YEAR, `x`)")) expect_equal(translate_sql(day(x)), sql("DATEPART(DAY, `x`)")) expect_equal(translate_sql(mday(x)), sql("DATEPART(DAY, `x`)")) expect_equal(translate_sql(yday(x)), sql("DATEPART(DAYOFYEAR, `x`)")) expect_equal(translate_sql(hour(x)), sql("DATEPART(HOUR, `x`)")) expect_equal(translate_sql(minute(x)), sql("DATEPART(MINUTE, `x`)")) expect_equal(translate_sql(second(x)), sql("DATEPART(SECOND, `x`)")) expect_equal(translate_sql(month(x)), sql("DATEPART(MONTH, `x`)")) expect_equal(translate_sql(month(x, label = TRUE, abbr = FALSE)), sql("DATENAME(MONTH, `x`)")) expect_error(translate_sql(month(x, abbr = TRUE, abbr = TRUE))) expect_equal(translate_sql(quarter(x)), sql("DATEPART(QUARTER, `x`)")) expect_equal(translate_sql(quarter(x, with_year = TRUE)), sql("(DATENAME(YEAR, `x`) + '.' + DATENAME(QUARTER, `x`))")) expect_error(translate_sql(quarter(x, fiscal_start = 5))) }) # verb translation -------------------------------------------------------- test_that("convert between bit and boolean as needed", { mf <- lazy_frame(x = 1, con = simulate_mssql()) # No conversion expect_snapshot(mf %>% filter(is.na(x))) expect_snapshot(mf %>% filter(!is.na(x))) expect_snapshot(mf %>% filter(x == 1L || x == 2L)) expect_snapshot(mf %>% mutate(z = ifelse(x == 1L, 1L, 2L))) expect_snapshot(mf %>% mutate(z = case_when(x == 1L ~ 1L))) # Single conversion on outer layer expect_snapshot(mf %>% mutate(z = !is.na(x))) expect_snapshot(mf %>% mutate(x = x == 1L)) expect_snapshot(mf %>% mutate(x = x == 1L || x == 2L)) expect_snapshot(mf %>% mutate(x = x == 1L || x == 2L || x == 3L)) expect_snapshot(mf %>% mutate(x = !(x == 1L || x == 2L || x == 3L))) }) test_that("handles ORDER BY in subqueries", { expect_snapshot( sql_query_select(simulate_mssql(), "x", "y", order_by = "z", subquery = TRUE) ) }) test_that("custom limit translation", { expect_snapshot( sql_query_select(simulate_mssql(), "x", "y", order_by = "z", limit = 10) ) }) test_that("custom escapes translated correctly", { mf <- lazy_frame(x = "abc", con = simulate_mssql()) a <- blob::as_blob("abc") b <- blob::as_blob(as.raw(c(0x01, 0x02))) L <- c(a, b) expect_snapshot(mf %>% filter(x == a)) expect_snapshot(mf %>% filter(x %in% L)) # expect_snapshot() also uses !! qry <- mf %>% filter(x %in% !!L) expect_snapshot(qry) }) test_that("logical escaping depends on context", { mf <- lazy_frame(x = "abc", con = simulate_mssql()) expect_snapshot(mf %>% filter(x == TRUE)) expect_snapshot(mf %>% mutate(x = TRUE)) }) test_that("generates custom sql", { con <- simulate_mssql() expect_snapshot(sql_table_analyze(con, in_schema("schema", "tbl"))) # Creates the same SQL since there's no temporary CLAUSE # Automatic renaming is handled upstream by db_collect()/db_copy_to() expect_snapshot(sql_query_save(con, sql("SELECT * FROM foo"), in_schema("schema", "tbl"))) expect_snapshot(sql_query_save(con, sql("SELECT * FROM foo"), in_schema("schema", "tbl"), temporary = FALSE)) }) # Live database ----------------------------------------------------------- test_that("can copy_to() and compute() with temporary tables (#272)", { con <- src_test("mssql") df <- tibble(x = 1:3) expect_message( db <- copy_to(con, df, name = "temp", temporary = TRUE), "Created a temporary table", ) expect_equal(db %>% pull(), 1:3) db2 <- expect_message( db %>% mutate(y = x + 1) %>% compute(), "Created a temporary table" ) expect_equal(db2 %>% pull(), 2:4) }) test_that("bit conversion works for important cases", { df <- tibble(x = 1:3, y = 3:1) db <- copy_to(src_test("mssql"), df, name = unique_table_name()) expect_equal(db %>% mutate(z = x == y) %>% pull(), c(FALSE, TRUE, FALSE)) expect_equal(db %>% filter(x == y) %>% pull(), 2) df <- tibble(x = c(TRUE, FALSE, FALSE), y = c(TRUE, FALSE, TRUE)) db <- copy_to(src_test("mssql"), df, name = unique_table_name()) expect_equal(db %>% filter(x == 1) %>% pull(), TRUE) expect_equal(db %>% mutate(z = TRUE) %>% pull(), c(1, 1, 1)) # Currently not supported: would need to determine that we have a bit # vector in a boolean context, and convert to boolean with x == 1. # expect_equal(db %>% mutate(z = x) %>% pull(), c(TRUE, FALSE, FALSE)) # expect_equal(db %>% mutate(z = !x) %>% pull(), c(FALSE, TRUE, TRUE)) # expect_equal(db %>% mutate(z = x & y) %>% pull(), c(TRUE, FALSE, FALSE)) }) test_that("as.integer and as.integer64 translations if parsing failures", { df <- data.frame(x = c("1.3", "2x")) db <- copy_to(src_test("mssql"), df, name = unique_table_name()) out <- db %>% mutate( integer = as.integer(x), integer64 = as.integer64(x), numeric = as.numeric(x), ) %>% collect() expect_identical(out$integer, c(1L, NA)) expect_identical(out$integer64, bit64::as.integer64(c(1L, NA))) expect_identical(out$numeric, c(1.3, NA)) }) dbplyr/tests/testthat/test-src_dbi.R0000644000176200001440000000031614002647450017257 0ustar liggesuserstest_that("tbl and src classes include connection class", { mf <- memdb_frame(x = 1, y = 2) expect_true(inherits(mf, "tbl_SQLiteConnection")) expect_true(inherits(mf$src, "src_SQLiteConnection")) }) dbplyr/tests/testthat/test-ident.R0000644000176200001440000000037414002647450016761 0ustar liggesuserstest_that("zero length inputs return correct clases", { expect_s3_class(ident(), "ident") }) test_that("ident quotes", { con <- simulate_dbi() x1 <- ident("x") expect_equal(escape(x1, con = con), sql('`x`')) expect_equal(as.sql(x1), x1) }) dbplyr/tests/testthat/test-verb-compute.R0000644000176200001440000000412714002647450020266 0ustar liggesuserstest_that("collect equivalent to as.data.frame/as_tibble", { mf <- memdb_frame(letters) expect_equal(as.data.frame(mf), data.frame(letters, stringsAsFactors = FALSE)) expect_equal(as_tibble(mf), tibble(letters)) expect_equal(collect(mf), tibble(letters)) }) test_that("explicit collection returns all data", { n <- 1e5 + 10 # previous default was 1e5 big <- memdb_frame(x = seq_len(n)) nrow1 <- big %>% as.data.frame() %>% nrow() nrow2 <- big %>% as_tibble() %>% nrow() nrow3 <- big %>% collect() %>% nrow() expect_equal(nrow1, n) expect_equal(nrow2, n) expect_equal(nrow3, n) }) test_that("compute doesn't change representation", { mf1 <- memdb_frame(x = 5:1, y = 1:5, z = "a") expect_equal_tbl(mf1, mf1 %>% compute) expect_equal_tbl(mf1, mf1 %>% compute %>% compute) mf2 <- mf1 %>% mutate(z = x + y) expect_equal_tbl(mf2, mf2 %>% compute) }) test_that("compute can create indexes", { mfs <- test_frame(x = 5:1, y = 1:5, z = 10) mfs %>% lapply(. %>% compute(indexes = c("x", "y"))) %>% expect_equal_tbls() mfs %>% lapply(. %>% compute(indexes = list("x", "y", c("x", "y")))) %>% expect_equal_tbls() mfs %>% lapply(. %>% compute(indexes = "x", unique_indexes = "y")) %>% expect_equal_tbls() mfs %>% lapply(. %>% compute(unique_indexes = list(c("x", "z"), c("y", "z")))) %>% expect_equal_tbls() }) test_that("unique index fails if values are duplicated", { mfs <- test_frame(x = 5:1, y = "a", ignore = "df") lapply(mfs, function(.) expect_error(compute(., unique_indexes = "y"))) }) test_that("compute creates correct column names", { out <- memdb_frame(x = 1) %>% group_by(x) %>% summarise(n = n()) %>% compute() %>% collect() expect_equal(out, tibble(x = 1, n = 1L)) }) # ops --------------------------------------------------------------------- test_that("sorting preserved across compute and collapse", { df1 <- memdb_frame(x = sample(10)) %>% arrange(x) df2 <- compute(df1) expect_equal(get_expr(op_sort(df2)[[1]]), quote(x)) df3 <- collapse(df1) expect_equal(get_expr(op_sort(df3)[[1]]), quote(x)) }) dbplyr/tests/testthat/test-sql-expr.R0000644000176200001440000000112414002647450017423 0ustar liggesuserstest_that("atomic vectors are escaped", { con <- simulate_dbi() expect_equal(sql_expr(2, con = con), sql("2.0")) expect_equal(sql_expr("x", con = con), sql("'x'")) }) test_that("user infix functions have % stripped", { con <- simulate_dbi() expect_equal(sql_expr(x %like% y, con = con), sql("x LIKE y")) }) test_that("string function names are not quoted", { con <- simulate_dbi() f <- "foo" expect_equal(sql_expr((!!f)(), con = con), sql("FOO()")) }) test_that("correct number of parens", { con <- simulate_dbi() expect_equal(sql_expr((1L), con = con), sql("(1)")) }) dbplyr/tests/testthat/test-verb-distinct.R0000644000176200001440000000452014004012136020415 0ustar liggesusersdf <- tibble( x = c(1, 1, 1, 1), y = c(1, 1, 2, 2), z = c(1, 2, 1, 2) ) dfs <- test_load(df) test_that("distinct equivalent to local unique when keep_all is TRUE", { dfs %>% lapply(. %>% distinct()) %>% expect_equal_tbls(unique(df)) }) test_that("distinct for single column equivalent to local unique (#1937)", { dfs %>% lapply(. %>% distinct(x, .keep_all = FALSE)) %>% expect_equal_tbls(unique(df["x"])) dfs %>% lapply(. %>% distinct(y, .keep_all = FALSE)) %>% expect_equal_tbls(unique(df["y"])) }) test_that("distinct throws error if column is specified and .keep_all is TRUE", { mf <- memdb_frame(x = 1:10) expect_snapshot_error(mf %>% distinct(x, .keep_all = TRUE) %>% collect()) }) test_that("distinct doesn't duplicate colum names if grouped (#354)", { df <- lazy_frame(a = 1) expect_equal(df %>% group_by(a) %>% distinct() %>% op_vars(), "a") }) test_that("distinct respects groups", { df <- memdb_frame(a = 1:2, b = 1) %>% group_by(a) expect_equal(df %>% group_by(a) %>% distinct() %>% op_vars(), c("a", "b")) }) # sql-render -------------------------------------------------------------- test_that("distinct adds DISTINCT suffix", { out <- memdb_frame(x = c(1, 1)) %>% distinct() expect_match(out %>% sql_render(), "SELECT DISTINCT") expect_equal(out %>% collect(), tibble(x = 1)) }) test_that("distinct can compute variables", { out <- memdb_frame(x = c(2, 1), y = c(1, 2)) %>% distinct(z = x + y) expect_equal(out %>% collect(), tibble(z = 3)) }) # sql_build --------------------------------------------------------------- test_that("distinct sets flagged", { out1 <- lazy_frame(x = 1) %>% select() %>% sql_build() expect_false(out1$distinct) out2 <- lazy_frame(x = 1) %>% distinct() %>% sql_build() expect_true(out2$distinct) }) # ops --------------------------------------------------------------------- test_that("distinct has complicated rules", { out <- lazy_frame(x = 1, y = 2) %>% distinct() expect_equal(op_vars(out), c("x", "y")) out <- lazy_frame(x = 1, y = 2, z = 3) %>% distinct(x, y) expect_equal(op_vars(out), c("x", "y")) out <- lazy_frame(x = 1, y = 2, z = 3) %>% distinct(a = x, b = y) expect_equal(op_vars(out), c("a", "b")) out <- lazy_frame(x = 1, y = 2, z = 3) %>% group_by(x) %>% distinct(y) expect_equal(op_vars(out), c("x", "y")) }) dbplyr/tests/testthat/_snaps/0000755000176200001440000000000014015732314016033 5ustar liggesusersdbplyr/tests/testthat/_snaps/verb-pivot-longer.md0000644000176200001440000000433014015732304021735 0ustar liggesusers# can pivot all cols to long Code lazy_frame(x = 1:2, y = 3:4) %>% tidyr::pivot_longer(x:y) Output (SELECT 'x' AS `name`, `x` AS `value` FROM `df`) UNION ALL (SELECT 'y' AS `name`, `y` AS `value` FROM `df`) # can add multiple columns from spec Code pv Output (SELECT 11 AS `a`, 13 AS `b`, `x` AS `v` FROM `df`) UNION ALL (SELECT 12 AS `a`, 14 AS `b`, `y` AS `v` FROM `df`) # preserves original keys Code pv Output (SELECT `x`, 'y' AS `name`, `y` AS `value` FROM `df`) UNION ALL (SELECT `x`, 'z' AS `name`, `z` AS `value` FROM `df`) # can drop missing values Code lazy_frame(x = c(1, NA), y = c(NA, 2)) %>% tidyr::pivot_longer(x:y, values_drop_na = TRUE) Output SELECT * FROM ((SELECT 'x' AS `name`, `x` AS `value` FROM `df`) UNION ALL (SELECT 'y' AS `name`, `y` AS `value` FROM `df`)) `q01` WHERE (NOT(((`value`) IS NULL))) # can handle missing combinations Code sql Output (SELECT `id`, `n`, `x`, NULL AS `y` FROM (SELECT `id`, '1' AS `n`, `x_1` AS `x` FROM `df`) `q01`) UNION ALL (SELECT `id`, '2' AS `n`, `x_2` AS `x`, `y_2` AS `y` FROM `df`) # can override default output column type Code lazy_frame(x = 1) %>% tidyr::pivot_longer(x, values_transform = list(value = as.character)) Output SELECT 'x' AS `name`, CAST(`x` AS TEXT) AS `value` FROM `df` # can pivot to multiple measure cols Code pv Output SELECT 1.0 AS `row`, `x` AS `X`, `y` AS `Y` FROM `df` # .value can be at any position in `names_to` Code value_first Output (SELECT `i`, 't1' AS `time`, `y_t1` AS `y`, `z_t1` AS `z` FROM `df`) UNION ALL (SELECT `i`, 't2' AS `time`, `y_t2` AS `y`, `z_t2` AS `z` FROM `df`) --- Code value_second Output (SELECT `i`, 't1' AS `time`, `t1_y` AS `y`, `t1_z` AS `z` FROM `df`) UNION ALL (SELECT `i`, 't2' AS `time`, `t2_y` AS `y`, `t2_z` AS `z` FROM `df`) dbplyr/tests/testthat/_snaps/backend-postgres.md0000644000176200001440000000103614015732273021614 0ustar liggesusers# custom SQL translation Code left_join(lf, lf, by = "x", na_matches = "na") Output SELECT `LHS`.`x` AS `x` FROM `df` AS `LHS` LEFT JOIN `df` AS `RHS` ON (`LHS`.`x` IS NOT DISTINCT FROM `RHS`.`x`) # can explain Code db %>% mutate(y = x + 1) %>% explain() Output SELECT "x", "x" + 1.0 AS "y" FROM "test" QUERY PLAN 1 Seq Scan on test (cost=0.00..1.04 rows=3 width=36) dbplyr/tests/testthat/_snaps/verb-mutate.md0000644000176200001440000000217414015732304020613 0ustar liggesusers# can refer to fresly created values Code show_query(out2) Output SELECT `x` + 4.0 AS `x` FROM (SELECT `x` + 2.0 AS `x` FROM (SELECT `x` + 1.0 AS `x` FROM `multi_mutate`)) # transmute includes all needed variables Code out Output SELECT `x`, `x` + `y` AS `x2` FROM (SELECT `x` / 2.0 AS `x`, `y` FROM `df`) `q01` # mutate generates subqueries as needed Code lf %>% mutate(x = x + 1, x = x + 1) Output SELECT `x` + 1.0 AS `x` FROM (SELECT `x` + 1.0 AS `x` FROM `df`) --- Code lf %>% mutate(x1 = x + 1, x2 = x1 + 1) Output SELECT `x`, `x1`, `x1` + 1.0 AS `x2` FROM (SELECT `x`, `x` + 1.0 AS `x1` FROM `df`) # mutate collapses over nested select Code lf %>% select(x:y) %>% mutate(x = x * 2, y = y * 2) Output SELECT `x` * 2.0 AS `x`, `y` * 2.0 AS `y` FROM `df` --- Code lf %>% select(y:x) %>% mutate(x = x * 2, y = y * 2) Output SELECT `y` * 2.0 AS `y`, `x` * 2.0 AS `x` FROM `df` dbplyr/tests/testthat/_snaps/sql-build.md0000644000176200001440000000020614015732275020255 0ustar liggesusers# rendering table wraps in SELECT * Code out %>% sql_render() Output SELECT * FROM `test-sql-build` dbplyr/tests/testthat/_snaps/backend-hana.md0000644000176200001440000000100014015732271020642 0ustar liggesusers# custom string translations Code translate_sql(paste0("a", "b")) Output 'a' || 'b' --- Code translate_sql(paste("a", "b")) Output 'a' || ' ' || 'b' --- Code translate_sql(substr(x, 2, 4)) Output SUBSTRING(`x`, 2, 3) --- Code translate_sql(substring(x, 2, 4)) Output SUBSTRING(`x`, 2, 3) --- Code translate_sql(str_sub(x, 2, -2)) Output SUBSTRING(`x`, 2, LENGTH(`x`) - 2) dbplyr/tests/testthat/_snaps/schema.md0000644000176200001440000000016314015732275017623 0ustar liggesusers# can construct and print Code in_schema("schema", "table") Output `schema`.`table` dbplyr/tests/testthat/_snaps/query-select.md0000644000176200001440000000043614015732275021010 0ustar liggesusers# select_query() print method output is as expected Code mf Output From: df Select: * # queries generated by select() don't alias unnecessarily Code lf_render Output SELECT `x` FROM `df` dbplyr/tests/testthat/_snaps/translate-sql-quantile.md0000644000176200001440000000125114015732276022775 0ustar liggesusers# quantile and median don't change without warning Code translate_sql(quantile(x, 0.75), window = FALSE) Output PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY `x`) --- Code translate_sql(quantile(x, 0.75), vars_group = "g") Output PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY `x`) OVER (PARTITION BY `g`) --- Code translate_sql(median(x, na.rm = TRUE), window = FALSE) Output PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY `x`) --- Code translate_sql(median(x, na.rm = TRUE), vars_group = "g") Output PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY `x`) OVER (PARTITION BY `g`) dbplyr/tests/testthat/_snaps/verb-distinct.md0000644000176200001440000000022314015732301021123 0ustar liggesusers# distinct throws error if column is specified and .keep_all is TRUE Can only find distinct value of specified columns if .keep_all is FALSE dbplyr/tests/testthat/_snaps/verb-summarise.md0000644000176200001440000000124514015732307021322 0ustar liggesusers# summarise(.groups=) Code eval_bare(expr(lazy_frame(x = 1, y = 2) %>% dplyr::group_by(x, y) %>% dplyr::summarise() %>% remote_query()), env(global_env())) Message `summarise()` has grouped output by 'x'. You can override using the `.groups` argument. Output SELECT `x`, `y` FROM `df` GROUP BY `x`, `y` --- `.groups` can't be "rowwise" in dbplyr i Possible values are NULL (default), "drop_last", "drop", and "keep" # quoting for rendering summarized grouped table Code out %>% sql_render Output SELECT `x`, COUNT(*) AS `n` FROM `verb-summarise` GROUP BY `x` dbplyr/tests/testthat/_snaps/verb-arrange.md0000644000176200001440000001237414015732277020747 0ustar liggesusers# quoting for rendering ordered grouped table Code sql_render(out) Output SELECT * FROM `test-verb-arrange` ORDER BY `y` # arrange renders correctly (#373) Code # # arrange renders correctly lf <- lazy_frame(a = 1:3, b = 3:1) # basic lf %>% arrange(a) Output SELECT * FROM `df` ORDER BY `a` Code # double arrange lf %>% arrange(a) %>% arrange(b) Warning ORDER BY is ignored in subqueries without LIMIT i Do you need to move arrange() later in the pipeline or use window_order() instead? Output SELECT * FROM `df` ORDER BY `b` Code # remove ordered by lf %>% arrange(a) %>% select(-a) Output SELECT `b` FROM `df` ORDER BY `a` Code lf %>% arrange(a) %>% select(-a) %>% arrange(b) Warning ORDER BY is ignored in subqueries without LIMIT i Do you need to move arrange() later in the pipeline or use window_order() instead? Output SELECT `b` FROM `df` ORDER BY `b` Code # un-arrange lf %>% arrange(a) %>% arrange() Output SELECT * FROM `df` ORDER BY `a` Code lf %>% arrange(a) %>% select(-a) %>% arrange() Output SELECT `b` FROM `df` ORDER BY `a` Code # use order lf %>% arrange(a) %>% select(-a) %>% mutate(c = lag(b)) Output SELECT `b`, LAG(`b`, 1, NULL) OVER (ORDER BY `a`) AS `c` FROM `df` ORDER BY `a` # arrange renders correctly for single-table verbs (#373) Code lf <- lazy_frame(a = 1:3, b = 3:1) # head lf %>% head(1) %>% arrange(a) Output SELECT * FROM (SELECT * FROM `df` LIMIT 1) `q01` ORDER BY `a` Code lf %>% arrange(a) %>% head(1) Output SELECT * FROM `df` ORDER BY `a` LIMIT 1 Code lf %>% arrange(a) %>% head(1) %>% arrange(b) Output SELECT * FROM (SELECT * FROM `df` ORDER BY `a` LIMIT 1) `q01` ORDER BY `b` Code # mutate lf %>% mutate(a = b) %>% arrange(a) Output SELECT `b` AS `a`, `b` FROM `df` ORDER BY `a` Code # complex mutate lf %>% arrange(a) %>% mutate(a = b) %>% arrange(a) Output SELECT `b` AS `a`, `b` FROM `df` ORDER BY `a` Code lf %>% arrange(a) %>% mutate(a = 1) %>% arrange(b) Warning ORDER BY is ignored in subqueries without LIMIT i Do you need to move arrange() later in the pipeline or use window_order() instead? Output SELECT 1.0 AS `a`, `b` FROM `df` ORDER BY `b` Code lf %>% mutate(a = -a) %>% arrange(a) %>% mutate(a = -a) Warning ORDER BY is ignored in subqueries without LIMIT i Do you need to move arrange() later in the pipeline or use window_order() instead? Output SELECT -`a` AS `a`, `b` FROM (SELECT -`a` AS `a`, `b` FROM `df`) `q01` # can combine arrange with dual table verbs Code lf <- lazy_frame(a = 1:3, b = 3:1) rf <- lazy_frame(a = 1:3, c = 4:6) # warn if arrange before join lf %>% arrange(a) %>% left_join(rf) Message Joining, by = "a" Warning ORDER BY is ignored in subqueries without LIMIT i Do you need to move arrange() later in the pipeline or use window_order() instead? Output SELECT `LHS`.`a` AS `a`, `b`, `c` FROM (SELECT * FROM `df`) `LHS` LEFT JOIN `df` AS `RHS` ON (`LHS`.`a` = `RHS`.`a`) Code lf %>% arrange(a) %>% semi_join(rf) Message Joining, by = "a" Warning ORDER BY is ignored in subqueries without LIMIT i Do you need to move arrange() later in the pipeline or use window_order() instead? Output SELECT * FROM (SELECT * FROM `df`) `LHS` WHERE EXISTS ( SELECT 1 FROM `df` AS `RHS` WHERE (`LHS`.`a` = `RHS`.`a`) ) Code lf %>% arrange(a) %>% union(rf) Output (SELECT `a`, `b`, NULL AS `c` FROM `df` ORDER BY `a`) UNION (SELECT `a`, NULL AS `b`, `c` FROM `df`) Code # can arrange after join lf %>% left_join(rf) %>% arrange(a) Message Joining, by = "a" Output SELECT * FROM (SELECT `LHS`.`a` AS `a`, `b`, `c` FROM `df` AS `LHS` LEFT JOIN `df` AS `RHS` ON (`LHS`.`a` = `RHS`.`a`) ) `q01` ORDER BY `a` Code lf %>% semi_join(rf) %>% arrange(a) Message Joining, by = "a" Output SELECT * FROM (SELECT * FROM `df` AS `LHS` WHERE EXISTS ( SELECT 1 FROM `df` AS `RHS` WHERE (`LHS`.`a` = `RHS`.`a`) )) `q01` ORDER BY `a` Code lf %>% union(rf) %>% arrange(a) Output SELECT * FROM ((SELECT `a`, `b`, NULL AS `c` FROM `df`) UNION (SELECT `a`, NULL AS `b`, `c` FROM `df`)) `q01` ORDER BY `a` dbplyr/tests/testthat/_snaps/verb-slice.md0000644000176200001440000000203414015732306020410 0ustar liggesusers# slice, head and tail aren't available slice() is not supported on database backends --- slice_head() is not supported on database backends i Please use slice_min() instead --- slice_tail() is not supported on database backends i Please use slice_max() instead # slice_min handles arguments Argument `order_by` is missing, with no default. --- Can only use `prop` when `with_ties = TRUE` # slice_max orders in opposite order Argument `order_by` is missing, with no default. # slice_sample errors when expected Sampling with replacement is not supported on database backends --- Weighted resampling is not supported on database backends --- Sampling by `prop` is not supported on database backends # check_slice_size checks for common issues Must supply exactly one of `n` and `prop` arguments. --- `n` must be a single number. --- `prop` must be a single number --- `n` must be a non-missing positive number. --- `prop` must be a non-missing positive number. dbplyr/tests/testthat/_snaps/backend-sqlite.md0000644000176200001440000000250014015732274021245 0ustar liggesusers# custom SQL translation Code left_join(lf, lf, by = "x", na_matches = "na") Output SELECT `LHS`.`x` AS `x` FROM `df` AS `LHS` LEFT JOIN `df` AS `RHS` ON (`LHS`.`x` IS `RHS`.`x`) # full and right join Code full_join(df1, df2, by = "x") Output SELECT `x`, `y.x`, `y.y`, `z` FROM (SELECT COALESCE(`LHS`.`x`, `RHS`.`x`) AS `x`, `LHS`.`y` AS `y.x`, `RHS`.`y` AS `y.y`, `z` FROM `df` AS `LHS` LEFT JOIN `df` AS `RHS` ON (`LHS`.`x` = `RHS`.`x`) UNION SELECT COALESCE(`LHS`.`x`, `RHS`.`x`) AS `x`, `LHS`.`y` AS `y.x`, `RHS`.`y` AS `y.y`, `z` FROM `df` AS `RHS` LEFT JOIN `df` AS `LHS` ON (`LHS`.`x` = `RHS`.`x`) ) AS `q01` --- Code right_join(df2, df1, by = "x") Output SELECT `RHS`.`x` AS `x`, `LHS`.`y` AS `y.x`, `z`, `RHS`.`y` AS `y.y` FROM `df` AS `RHS` LEFT JOIN `df` AS `LHS` ON (`LHS`.`x` = `RHS`.`x`) # can explain a query Code db %>% filter(x > 2) %>% explain() Output SELECT * FROM `test` WHERE (`x` > 2.0) id parent notused detail 1 2 0 0 SEARCH TABLE test USING COVERING INDEX test_x (x>?) dbplyr/tests/testthat/_snaps/backend-impala.md0000644000176200001440000000021714015732271021207 0ustar liggesusers# generates custom sql Code sql_table_analyze(con, in_schema("schema", "tbl")) Output COMPUTE STATS `schema`.`tbl` dbplyr/tests/testthat/_snaps/backend-oracle.md0000644000176200001440000000134014015732273021211 0ustar liggesusers# queries translate correctly Code mf %>% head() Output SELECT * FROM (`df`) FETCH FIRST 6 ROWS ONLY # generates custom sql Code sql_table_analyze(con, in_schema("schema", "tbl")) Output ANALYZE TABLE `schema`.`tbl` COMPUTE STATISTICS --- Code sql_query_explain(con, sql("SELECT * FROM foo")) Output EXPLAIN PLAN FOR SELECT * FROM foo; SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY())); --- Code left_join(lf, lf, by = "x", na_matches = "na") Output SELECT `LHS`.`x` AS `x` FROM (`df`) `LHS` LEFT JOIN (`df`) `RHS` ON (decode(`LHS`.`x`, `RHS`.`x`, 0, 1) = 0) dbplyr/tests/testthat/_snaps/translate-sql.md0000644000176200001440000000031314015732277021154 0ustar liggesusers# namespace calls are translated There is no package called 'NOSUCHPACKAGE' --- 'NOSUCHFUNCTION' is not an exported object from 'dbplyr' --- No known translation for base::abbreviate() dbplyr/tests/testthat/_snaps/verb-select.md0000644000176200001440000000151014015732306020566 0ustar liggesusers# select preserves grouping vars Code out <- mf %>% select(a) %>% collect() Message Adding missing grouping variables: `b` # multiple selects are collapsed Code lf %>% select(2:1) %>% select(2:1) Output SELECT `x`, `y` FROM `df` --- Code lf %>% select(2:1) %>% select(2:1) %>% select(2:1) Output SELECT `y`, `x` FROM `df` --- Code lf %>% select(x1 = x) %>% select(x2 = x1) Output SELECT `x` AS `x2` FROM `df` # mutate collapses over nested select Code lf %>% mutate(a = 1, b = 2) %>% select(a) Output SELECT 1.0 AS `a` FROM `df` --- Code lf %>% mutate(a = 1, b = 2) %>% select(x) Output SELECT `x` FROM `df` dbplyr/tests/testthat/_snaps/query-semi-join.md0000644000176200001440000000152214015732275021420 0ustar liggesusers# print method doesn't change unexpectedly Code sql_build(semi_join(lf1, lf2)) Message Joining, by = "x" Output By: x-x X: df Y: df # generated sql doesn't change unexpectedly Code semi_join(lf, lf) Message Joining, by = c("x", "y") Output SELECT * FROM `df` AS `LHS` WHERE EXISTS ( SELECT 1 FROM `df` AS `RHS` WHERE (`LHS`.`x` = `RHS`.`x` AND `LHS`.`y` = `RHS`.`y`) ) --- Code anti_join(lf, lf) Message Joining, by = c("x", "y") Output SELECT * FROM `df` AS `LHS` WHERE NOT EXISTS ( SELECT 1 FROM `df` AS `RHS` WHERE (`LHS`.`x` = `RHS`.`x` AND `LHS`.`y` = `RHS`.`y`) ) dbplyr/tests/testthat/_snaps/backend-.md0000644000176200001440000000277214015732271020033 0ustar liggesusers# default raw escapes translated correctly Code mf %>% filter(x == a) Output SELECT * FROM `df` WHERE (`x` = X'616263') --- Code mf %>% filter(x %in% L) Output SELECT * FROM `df` WHERE (`x` IN (X'616263', X'0102')) --- Code qry Output SELECT * FROM `df` WHERE (`x` IN (X'616263', X'0102')) # DDL operations generate expected SQL Code sql_table_analyze(con, in_schema("schema", "tbl")) Output ANALYZE `schema`.`tbl` --- Code sql_query_explain(con, sql("SELECT * FROM foo")) Output EXPLAIN SELECT * FROM foo --- Code sql_query_wrap(con, ident("table")) Output table --- Code sql_query_wrap(con, in_schema("schema", "tbl")) Output `schema`.`tbl` --- Code sql_query_wrap(con, sql("SELECT * FROM foo")) Output (SELECT * FROM foo) `q03` --- Code sql_table_index(con, in_schema("schema", "tbl"), c("a", "b")) Output CREATE INDEX `schema_tbl_a_b` ON `schema`.`tbl` (`a`, `b`) --- Code sql_table_index(con, in_schema("schema", "tbl"), "c", unique = TRUE) Output CREATE UNIQUE INDEX `schema_tbl_c` ON `schema`.`tbl` (`c`) --- Code sql_query_save(con, sql("SELECT * FROM foo"), in_schema("temp", "tbl")) Output CREATE TEMPORARY TABLE `temp`.`tbl` AS SELECT * FROM foo dbplyr/tests/testthat/_snaps/backend-hive.md0000644000176200001440000000025614015732271020702 0ustar liggesusers# generates custom sql Code sql_table_analyze(simulate_hive(), in_schema("schema", "tbl")) Output ANALYZE TABLE `schema`.`tbl` COMPUTE STATISTICS dbplyr/tests/testthat/_snaps/verb-count.md0000644000176200001440000000127114015732300020435 0ustar liggesusers# generates expected SQL for common situations Code db %>% count(g) Output SELECT `g`, COUNT(*) AS `n` FROM `df` GROUP BY `g` --- Code db %>% count(g, wt = x) Output SELECT `g`, SUM(`x`) AS `n` FROM `df` GROUP BY `g` --- Code db %>% count(g, sort = TRUE) Output SELECT `g`, COUNT(*) AS `n` FROM `df` GROUP BY `g` ORDER BY `n` DESC # complains about bad names Code db <- lazy_frame(g = 1, x = 2) db %>% count(g, name = "g") Error 'g' already present in output i Use `name = "new_name"` to pick a new name. dbplyr/tests/testthat/_snaps/backend-teradata.md0000644000176200001440000000042314015732274021533 0ustar liggesusers# generates custom sql Code sql_table_analyze(con, in_schema("schema", "tbl")) Output COLLECT STATISTICS `schema`.`tbl` # head translated to TOP Code mf %>% head() %>% sql_render() Output SELECT TOP 6 * FROM `df` dbplyr/tests/testthat/_snaps/translate-sql-string.md0000644000176200001440000000071614015732276022466 0ustar liggesusers# sql_substr works as expected Code substr("test") Error argument "start" is missing, with no default --- Code substr("test", 0) Error argument "stop" is missing, with no default --- Code substr("test", "x", 1) Error `start` must be a single number --- Code substr("test", 1, "x") Error `stop` must be a single number dbplyr/tests/testthat/_snaps/backend-access.md0000644000176200001440000000017114031447100021174 0ustar liggesusers# queries translate correctly Code mf %>% head() Output SELECT TOP 6 * FROM `df` dbplyr/tests/testthat/_snaps/partial-eval.md0000644000176200001440000000575014015732274020752 0ustar liggesusers# across() translates character vectors Code lf %>% summarise(across(a:b, "log")) Output SELECT LN(`a`) AS `a`, LN(`b`) AS `b` FROM `df` --- Code lf %>% summarise(across(a:b, "log", base = 2)) Output SELECT LOG(2.0, `a`) AS `a`, LOG(2.0, `b`) AS `b` FROM `df` --- Code lf %>% summarise(across(a, c("log", "exp"))) Output SELECT LN(`a`) AS `a_log`, EXP(`a`) AS `a_exp` FROM `df` # across() translates functions Code lf %>% summarise(across(a:b, log)) Output SELECT LN(`a`) AS `a`, LN(`b`) AS `b` FROM `df` --- Code lf %>% summarise(across(a:b, log, base = 2)) Output SELECT LOG(2.0, `a`) AS `a`, LOG(2.0, `b`) AS `b` FROM `df` --- Code lf %>% summarise(across(a:b, list(log, exp))) Output SELECT LN(`a`) AS `a_log`, EXP(`a`) AS `a_exp`, LN(`b`) AS `b_log`, EXP(`b`) AS `b_exp` FROM `df` # untranslatable functions are preserved Code lf %>% summarise(across(a:b, SQL_LOG)) Output SELECT SQL_LOG(`a`) AS `a`, SQL_LOG(`b`) AS `b` FROM `df` # across() translates formulas Code lf %>% summarise(across(a:b, ~log(.x, 2))) Output SELECT LOG(2.0, `a`) AS `a`, LOG(2.0, `b`) AS `b` FROM `df` --- Code lf %>% summarise(across(a:b, list(~log(.x, 2)))) Output SELECT LOG(2.0, `a`) AS `a`, LOG(2.0, `b`) AS `b` FROM `df` # across() translates NULL Code lf %>% mutate(across(a:b)) Output SELECT `a`, `b` FROM `df` # old _at functions continue to work Code lf %>% dplyr::summarise_at(dplyr::vars(a:b), "sum") Warning Missing values are always removed in SQL. Use `SUM(x, na.rm = TRUE)` to silence this warning This warning is displayed only once per session. Output SELECT SUM(`a`) AS `a`, SUM(`b`) AS `b` FROM `df` --- Code lf %>% dplyr::summarise_at(dplyr::vars(a:b), sum) Output SELECT SUM(`a`) AS `a`, SUM(`b`) AS `b` FROM `df` --- Code lf %>% dplyr::summarise_at(dplyr::vars(a:b), ~sum(.)) Output SELECT SUM(`a`) AS `a`, SUM(`b`) AS `b` FROM `df` # if_all/any works in filter() Code lf %>% filter(if_all(a:b, ~. > 0)) Output SELECT * FROM `df` WHERE (`a` > 0.0 AND `b` > 0.0) --- Code lf %>% filter(if_any(a:b, ~. > 0)) Output SELECT * FROM `df` WHERE (`a` > 0.0 OR `b` > 0.0) # if_all/any works in mutate() Code lf %>% mutate(c = if_all(a:b, ~. > 0)) Output SELECT `a`, `b`, `a` > 0.0 AND `b` > 0.0 AS `c` FROM `df` --- Code lf %>% mutate(c = if_any(a:b, ~. > 0)) Output SELECT `a`, `b`, `a` > 0.0 OR `b` > 0.0 AS `c` FROM `df` dbplyr/tests/testthat/_snaps/query-join.md0000644000176200001440000000654514015732275020477 0ustar liggesusers# print method doesn't change unexpectedly Code sql_build(left_join(lf1, lf2)) Message Joining, by = "x" Output By: x-x X: df Y: df # generated sql doesn't change unexpectedly Code inner_join(lf, lf) Message Joining, by = c("x", "y") Output SELECT `LHS`.`x` AS `x`, `LHS`.`y` AS `y` FROM `df` AS `LHS` INNER JOIN `df` AS `RHS` ON (`LHS`.`x` = `RHS`.`x` AND `LHS`.`y` = `RHS`.`y`) --- Code left_join(lf, lf) Message Joining, by = c("x", "y") Output SELECT `LHS`.`x` AS `x`, `LHS`.`y` AS `y` FROM `df` AS `LHS` LEFT JOIN `df` AS `RHS` ON (`LHS`.`x` = `RHS`.`x` AND `LHS`.`y` = `RHS`.`y`) --- Code right_join(lf, lf) Message Joining, by = c("x", "y") Output SELECT `RHS`.`x` AS `x`, `RHS`.`y` AS `y` FROM `df` AS `LHS` RIGHT JOIN `df` AS `RHS` ON (`LHS`.`x` = `RHS`.`x` AND `LHS`.`y` = `RHS`.`y`) --- Code full_join(lf, lf) Message Joining, by = c("x", "y") Output SELECT COALESCE(`LHS`.`x`, `RHS`.`x`) AS `x`, COALESCE(`LHS`.`y`, `RHS`.`y`) AS `y` FROM `df` AS `LHS` FULL JOIN `df` AS `RHS` ON (`LHS`.`x` = `RHS`.`x` AND `LHS`.`y` = `RHS`.`y`) # only disambiguates shared variables Code left_join(lf1, lf2) Message Joining, by = "x" Output SELECT `LHS`.`x` AS `x`, `y`, `z` FROM `df` AS `LHS` LEFT JOIN `df` AS `RHS` ON (`LHS`.`x` = `RHS`.`x`) --- Code left_join(lf1, lf2, by = c(y = "z")) Output SELECT `LHS`.`x` AS `x.x`, `y`, `RHS`.`x` AS `x.y` FROM `df` AS `LHS` LEFT JOIN `df` AS `RHS` ON (`LHS`.`y` = `RHS`.`z`) # sql_on query doesn't change unexpectedly Code inner_join(lf1, lf2, sql_on = "LHS.y < RHS.z") Output SELECT `LHS`.`x` AS `x.x`, `y`, `RHS`.`x` AS `x.y`, `z` FROM `df` AS `LHS` INNER JOIN `df` AS `RHS` ON (LHS.y < RHS.z) --- Code left_join(lf1, lf2, sql_on = "LHS.y < RHS.z") Output SELECT `LHS`.`x` AS `x.x`, `y`, `RHS`.`x` AS `x.y`, `z` FROM `df` AS `LHS` LEFT JOIN `df` AS `RHS` ON (LHS.y < RHS.z) --- Code right_join(lf1, lf2, sql_on = "LHS.y < RHS.z") Output SELECT `LHS`.`x` AS `x.x`, `y`, `RHS`.`x` AS `x.y`, `z` FROM `df` AS `LHS` RIGHT JOIN `df` AS `RHS` ON (LHS.y < RHS.z) --- Code full_join(lf1, lf2, sql_on = "LHS.y < RHS.z") Output SELECT `LHS`.`x` AS `x.x`, `y`, `RHS`.`x` AS `x.y`, `z` FROM `df` AS `LHS` FULL JOIN `df` AS `RHS` ON (LHS.y < RHS.z) --- Code semi_join(lf1, lf2, sql_on = "LHS.y < RHS.z") Output SELECT * FROM `df` AS `LHS` WHERE EXISTS ( SELECT 1 FROM `df` AS `RHS` WHERE (LHS.y < RHS.z) ) --- Code anti_join(lf1, lf2, sql_on = "LHS.y < RHS.z") Output SELECT * FROM `df` AS `LHS` WHERE NOT EXISTS ( SELECT 1 FROM `df` AS `RHS` WHERE (LHS.y < RHS.z) ) dbplyr/tests/testthat/_snaps/translate-sql-conditional.md0000644000176200001440000000106314015732276023457 0ustar liggesusers# case_when converted to CASE WHEN Code translate_sql(case_when(x > 1L ~ "a")) Output CASE WHEN (`x` > 1) THEN ('a') END # even inside mutate Code out$select[[2]] Output [1] "CASE\nWHEN (`x` > 1) THEN ('a')\nEND" # case_when translates correctly to ELSE when TRUE ~ is used 2 Code translate_sql(case_when(x == 1L ~ "yes", x == 0L ~ "no", TRUE ~ "undefined")) Output CASE WHEN (`x` = 1) THEN ('yes') WHEN (`x` = 0) THEN ('no') ELSE ('undefined') END dbplyr/tests/testthat/_snaps/verb-joins.md0000644000176200001440000000050014015732303020424 0ustar liggesusers# can optionally match NA values Code left_join(lf, lf, by = "x", na_matches = "na") Output SELECT `LHS`.`x` AS `x` FROM `df` AS `LHS` LEFT JOIN `df` AS `RHS` ON (CASE WHEN (`LHS`.`x` = `RHS`.`x`) OR (`LHS`.`x` IS NULL AND `RHS`.`x` IS NULL) THEN 0 ELSE 1 = 0) dbplyr/tests/testthat/_snaps/verb-fill.md0000644000176200001440000000411314015732302020233 0ustar liggesusers# up-direction works Code df_lazy_ns %>% window_order(id) %>% tidyr::fill(n1, .direction = "up") Output SELECT `id`, `group`, MAX(`n1`) OVER (PARTITION BY `..dbplyr_partion_1`) AS `n1` FROM (SELECT `id`, `group`, `n1`, SUM(CASE WHEN (((`n1`) IS NULL)) THEN (0) WHEN NOT(((`n1`) IS NULL)) THEN (1) END) OVER (ORDER BY -`id` ROWS UNBOUNDED PRECEDING) AS `..dbplyr_partion_1` FROM `df`) --- Code df_lazy_std %>% window_order(id) %>% tidyr::fill(n1, .direction = "up") Output SELECT `id`, `group`, LAST_VALUE(`n1` IGNORE NULLS) OVER (ORDER BY -`id`) AS `n1` FROM `df` # up-direction works with descending Code df_lazy_ns %>% window_order(desc(id)) %>% tidyr::fill(n1, .direction = "up") Output SELECT `id`, `group`, MAX(`n1`) OVER (PARTITION BY `..dbplyr_partion_1`) AS `n1` FROM (SELECT `id`, `group`, `n1`, SUM(CASE WHEN (((`n1`) IS NULL)) THEN (0) WHEN NOT(((`n1`) IS NULL)) THEN (1) END) OVER (ORDER BY -`id` DESC ROWS UNBOUNDED PRECEDING) AS `..dbplyr_partion_1` FROM `df`) --- Code df_lazy_std %>% window_order(desc(id)) %>% tidyr::fill(n1, .direction = "up") Output SELECT `id`, `group`, LAST_VALUE(`n1` IGNORE NULLS) OVER (ORDER BY -`id` DESC) AS `n1` FROM `df` # groups are respected Code group_by(df_lazy_ns, group) %>% window_order(id) %>% tidyr::fill(n1) Output SELECT `id`, `group`, MAX(`n1`) OVER (PARTITION BY `group`, `..dbplyr_partion_1`) AS `n1` FROM (SELECT `id`, `group`, `n1`, SUM(CASE WHEN (((`n1`) IS NULL)) THEN (0) WHEN NOT(((`n1`) IS NULL)) THEN (1) END) OVER (PARTITION BY `group` ORDER BY `id` ROWS UNBOUNDED PRECEDING) AS `..dbplyr_partion_1` FROM `df`) --- Code group_by(df_lazy_std, group) %>% window_order(id) %>% tidyr::fill(n1) Output SELECT `id`, `group`, LAST_VALUE(`n1` IGNORE NULLS) OVER (PARTITION BY `group` ORDER BY `id`) AS `n1` FROM `df` # fill errors on unsorted data Code expect_error(df_db %>% tidyr::fill(n1)) dbplyr/tests/testthat/_snaps/backend-mssql.md0000644000176200001440000000642014015732272021106 0ustar liggesusers# convert between bit and boolean as needed Code mf %>% filter(is.na(x)) Output SELECT * FROM `df` WHERE (((`x`) IS NULL)) --- Code mf %>% filter(!is.na(x)) Output SELECT * FROM `df` WHERE (NOT(((`x`) IS NULL))) --- Code mf %>% filter(x == 1L || x == 2L) Output SELECT * FROM `df` WHERE (`x` = 1 OR `x` = 2) --- Code mf %>% mutate(z = ifelse(x == 1L, 1L, 2L)) Output SELECT `x`, IIF(`x` = 1, 1, 2) AS `z` FROM `df` --- Code mf %>% mutate(z = case_when(x == 1L ~ 1L)) Output SELECT `x`, CASE WHEN (`x` = 1) THEN (1) END AS `z` FROM `df` --- Code mf %>% mutate(z = !is.na(x)) Output SELECT `x`, CAST(IIF(~((`x`) IS NULL), 1, 0) AS BIT) AS `z` FROM `df` --- Code mf %>% mutate(x = x == 1L) Output SELECT CAST(IIF(`x` = 1, 1, 0) AS BIT) AS `x` FROM `df` --- Code mf %>% mutate(x = x == 1L || x == 2L) Output SELECT CAST(IIF(`x` = 1 OR `x` = 2, 1, 0) AS BIT) AS `x` FROM `df` --- Code mf %>% mutate(x = x == 1L || x == 2L || x == 3L) Output SELECT CAST(IIF(`x` = 1 OR `x` = 2 OR `x` = 3, 1, 0) AS BIT) AS `x` FROM `df` --- Code mf %>% mutate(x = !(x == 1L || x == 2L || x == 3L)) Output SELECT CAST(IIF(~(`x` = 1 OR `x` = 2 OR `x` = 3), 1, 0) AS BIT) AS `x` FROM `df` # handles ORDER BY in subqueries Code sql_query_select(simulate_mssql(), "x", "y", order_by = "z", subquery = TRUE) Warning ORDER BY is ignored in subqueries without LIMIT i Do you need to move arrange() later in the pipeline or use window_order() instead? Output SELECT 'x' FROM 'y' # custom limit translation Code sql_query_select(simulate_mssql(), "x", "y", order_by = "z", limit = 10) Output SELECT TOP 10 'x' FROM 'y' ORDER BY 'z' # custom escapes translated correctly Code mf %>% filter(x == a) Output SELECT * FROM `df` WHERE (`x` = 0x616263) --- Code mf %>% filter(x %in% L) Output SELECT * FROM `df` WHERE (`x` IN (0x616263, 0x0102)) --- Code qry Output SELECT * FROM `df` WHERE (`x` IN (0x616263, 0x0102)) # logical escaping depends on context Code mf %>% filter(x == TRUE) Output SELECT * FROM `df` WHERE (`x` = TRUE) --- Code mf %>% mutate(x = TRUE) Output SELECT 1 AS `x` FROM `df` # generates custom sql Code sql_table_analyze(con, in_schema("schema", "tbl")) Output UPDATE STATISTICS `schema`.`tbl` --- Code sql_query_save(con, sql("SELECT * FROM foo"), in_schema("schema", "tbl")) Output SELECT * INTO `schema`.`tbl` FROM (SELECT * FROM foo) AS temp --- Code sql_query_save(con, sql("SELECT * FROM foo"), in_schema("schema", "tbl"), temporary = FALSE) Output SELECT * INTO `schema`.`tbl` FROM (SELECT * FROM foo) AS temp dbplyr/tests/testthat/_snaps/query-set-op.md0000644000176200001440000000232614015732275020740 0ustar liggesusers# print method doesn't change unexpectedly Code sql_build(union(lf1, lf2)) Output X: From: df Select: `x`, `y`, NULL Y: From: df Select: `x`, NULL, `z` # generated sql doesn't change unexpectedly Code union(lf, lf) Output (SELECT * FROM `df`) UNION (SELECT * FROM `df`) --- Code setdiff(lf, lf) Output (SELECT * FROM `df`) EXCEPT (SELECT * FROM `df`) --- Code intersect(lf, lf) Output (SELECT * FROM `df`) INTERSECT (SELECT * FROM `df`) --- Code union(lf, lf, all = TRUE) Output (SELECT * FROM `df`) UNION ALL (SELECT * FROM `df`) --- Code setdiff(lf, lf, all = TRUE) Output (SELECT * FROM `df`) EXCEPT ALL (SELECT * FROM `df`) --- Code intersect(lf, lf, all = TRUE) Output (SELECT * FROM `df`) INTERSECT ALL (SELECT * FROM `df`) dbplyr/tests/testthat/_snaps/verb-pivot-wider.md0000644000176200001440000000377714015732305021600 0ustar liggesusers# can pivot all cols to wide Code lazy_frame(key = c("x", "y", "z"), val = 1:3) %>% dbplyr_pivot_wider_spec(spec) Output SELECT MAX(CASE WHEN (`key` = 'x') THEN (`val`) END) AS `x`, MAX(CASE WHEN (`key` = 'y') THEN (`val`) END) AS `y`, MAX(CASE WHEN (`key` = 'z') THEN (`val`) END) AS `z` FROM `df` # implicit missings turn into explicit missings Code lazy_frame(a = 1:2, key = c("x", "y"), val = 1:2) %>% dbplyr_pivot_wider_spec( spec) Output SELECT `a`, MAX(CASE WHEN (`key` = 'x') THEN (`val`) END) AS `x`, MAX(CASE WHEN (`key` = 'y') THEN (`val`) END) AS `y` FROM `df` GROUP BY `a` # error when overwriting existing column Names must be unique. x These names are duplicated: * "a" at locations 1 and 2. # values_fn can be a single function Code dbplyr_pivot_wider_spec(df, spec1, values_fn = sum) Output SELECT `a`, SUM(CASE WHEN (`key` = 'x') THEN (`val`) END) AS `x` FROM `df` GROUP BY `a` # values_fn cannot be NULL `values_fn` must not be NULL i `values_fn` must be a function or a named list of functions # can fill in missing cells Code dbplyr_pivot_wider_spec(df_lazy, spec, values_fill = 0) Output SELECT `g`, `name`, `value`, MAX(CASE WHEN (`key` = 'x') THEN (`val`) WHEN NOT(`key` = 'x') THEN (0.0) END) AS `x`, MAX(CASE WHEN (`key` = 'y') THEN (`val`) WHEN NOT(`key` = 'y') THEN (0.0) END) AS `y` FROM `df` GROUP BY `g`, `name`, `value` --- Code dbplyr_pivot_wider_spec(df_lazy, spec, values_fill = list(value = 0)) Output SELECT `g`, `name`, `value`, MAX(CASE WHEN (`key` = 'x') THEN (`val`) END) AS `x`, MAX(CASE WHEN (`key` = 'y') THEN (`val`) END) AS `y` FROM `df` GROUP BY `g`, `name`, `value` # cannot pivot lazy frames `dbplyr_build_wider_spec()` doesn't work with local lazy tibbles. i Use `memdb_frame()` together with `show_query()` to see the SQL code. dbplyr/tests/testthat/_snaps/verb-expand.md0000644000176200001440000000442214015732301020566 0ustar liggesusers# expand completes all values Code lazy_frame(x = 1, y = 1) %>% tidyr::expand(x, y) Output SELECT `x`, `y` FROM (SELECT DISTINCT `x` FROM `df`) `LHS` LEFT JOIN (SELECT DISTINCT `y` FROM `df`) `RHS` # nesting doesn't expand values Code df_lazy %>% tidyr::expand(nesting(x, y)) Output SELECT DISTINCT `x`, `y` FROM `df` # expand accepts expressions Code tidyr::expand(df, round(x / 2)) Output SELECT DISTINCT ROUND(`x` / 2.0, 0) AS `round(x/2)` FROM `df` --- Code tidyr::expand(df, nesting(x_half = round(x / 2), x1 = x + 1)) Output SELECT DISTINCT ROUND(`x` / 2.0, 0) AS `x_half`, `x` + 1.0 AS `x1` FROM `df` # expand respects groups Code df_lazy %>% group_by(a) %>% tidyr::expand(b, c) Output SELECT `LHS`.`a` AS `a`, `b`, `c` FROM (SELECT DISTINCT `a`, `b` FROM `df`) `LHS` LEFT JOIN (SELECT DISTINCT `a`, `c` FROM `df`) `RHS` ON (`LHS`.`a` = `RHS`.`a`) # NULL inputs Code tidyr::expand(lazy_frame(x = 1), x, y = NULL) Output SELECT DISTINCT `x` FROM `df` # expand() errors when expected Must supply variables in `...` --- Must supply variables in `...` # replace_na replaces missing values Code lazy_frame(x = 1, y = "a") %>% tidyr::replace_na(list(x = 0, y = "unknown")) Output SELECT COALESCE(`x`, 0.0) AS `x`, COALESCE(`y`, 'unknown') AS `y` FROM `df` # replace_na ignores missing columns Code lazy_frame(x = 1) %>% tidyr::replace_na(list(not_there = 0)) Output SELECT * FROM `df` # complete completes missing combinations Code df_lazy %>% tidyr::complete(x, y, fill = list(z = "c")) Output SELECT `x`, `y`, COALESCE(`z`, 'c') AS `z` FROM (SELECT COALESCE(`LHS`.`x`, `RHS`.`x`) AS `x`, COALESCE(`LHS`.`y`, `RHS`.`y`) AS `y`, `z` FROM (SELECT `x`, `y` FROM (SELECT DISTINCT `x` FROM `df`) `LHS` LEFT JOIN (SELECT DISTINCT `y` FROM `df`) `RHS` ) `LHS` FULL JOIN `df` AS `RHS` ON (`LHS`.`x` = `RHS`.`x` AND `LHS`.`y` = `RHS`.`y`) ) `q01` dbplyr/tests/testthat/_snaps/backend-mysql.md0000644000176200001440000000144414015732272021115 0ustar liggesusers# generates custom sql Code sql_table_analyze(con, in_schema("schema", "tbl")) Output ANALYZE TABLE `schema`.`tbl` --- Code sql_query_explain(con, sql("SELECT * FROM table")) Output EXPLAIN SELECT * FROM table --- Code left_join(lf, lf, by = "x", na_matches = "na") Output SELECT `LHS`.`x` AS `x` FROM `df` AS `LHS` LEFT JOIN `df` AS `RHS` ON (`LHS`.`x` <=> `RHS`.`x`) # can explain Code db %>% mutate(y = x + 1) %>% explain() Output SELECT `x`, `x` + 1.0 AS `y` FROM `test` id select_type table type possible_keys key key_len ref rows Extra 1 1 SIMPLE test ALL 3 dbplyr/tests/testthat/_snaps/translate-sql-helpers.md0000644000176200001440000000056414015732276022623 0ustar liggesusers# output of print method for sql_variant is correct Code sql_variant(sim_trans, sim_trans, sim_trans) Output scalar: + aggregate: + window: + # win_rank() is accepted by the sql_translator Code sql_variant(sql_translator(test = win_rank("test"))) Output scalar: test dbplyr/tests/testthat/_snaps/escape.md0000644000176200001440000000042114015732274017617 0ustar liggesusers# shiny objects give useful errors Cannot translate shiny inputs to SQL. * Force evaluation in R with (e.g.) `!!inputs$x` or `local(inputs$x)` --- Cannot translate a shiny reactive to SQL. * Force evaluation in R with (e.g.) `!!foo()` or `local(foo())` dbplyr/tests/testthat/_snaps/tbl-lazy.md0000644000176200001440000000015314015732275020120 0ustar liggesusers# has print method Code tbl_lazy(mtcars) Output SELECT * FROM `df` dbplyr/tests/testthat/_snaps/verb-uncount.md0000644000176200001440000000047114015732307021010 0ustar liggesusers# symbols weights are dropped in output Code dbplyr_uncount(df, w) %>% show_query() Output SELECT `x` FROM (SELECT `x`, `w`, `..dbplyr_row_id` FROM `dbplyr_2001` AS `LHS` INNER JOIN `dbplyr_2003` AS `RHS` ON (`RHS`.`..dbplyr_row_id` <= `LHS`.`w`) ) dbplyr/tests/testthat/test-translate-sql-string.R0000644000176200001440000000522114006567571021761 0ustar liggesuserstest_that("sql_substr works as expected", { local_con(simulate_dbi()) x <- ident("x") substr <- sql_substr("SUBSTR") expect_equal(substr(x, 3, 4), sql("SUBSTR(`x`, 3, 2)")) expect_equal(substr(x, 3, 3), sql("SUBSTR(`x`, 3, 1)")) expect_equal(substr(x, 3, 2), sql("SUBSTR(`x`, 3, 0)")) expect_equal(substr(x, 3, 1), sql("SUBSTR(`x`, 3, 0)")) expect_equal(substr(x, 0, 1), sql("SUBSTR(`x`, 1, 1)")) expect_equal(substr(x, -1, 1), sql("SUBSTR(`x`, 1, 1)")) # Missing arguments expect_snapshot(error = TRUE, substr("test")) expect_snapshot(error = TRUE, substr("test", 0)) # Wrong types expect_snapshot(error = TRUE, substr("test", "x", 1)) expect_snapshot(error = TRUE, substr("test", 1, "x")) }) test_that("substring is also translated", { expect_equal(translate_sql(substring(x, 3, 4)), sql("SUBSTR(`x`, 3, 2)")) }) test_that("sql_str_sub works as expected", { local_con(simulate_dbi()) x <- ident("x") str_sub <- sql_str_sub("SUBSTR") expect_equal(str_sub(x), sql("SUBSTR(`x`, 1)")) expect_equal(str_sub(x, 1), sql("SUBSTR(`x`, 1)")) expect_equal(str_sub(x, -1), sql("SUBSTR(`x`, LENGTH(`x`))")) expect_equal(str_sub(x, 2, 4), sql("SUBSTR(`x`, 2, 3)")) expect_equal(str_sub(x, 2, 2), sql("SUBSTR(`x`, 2, 1)")) expect_equal(str_sub(x, 2, 0), sql("SUBSTR(`x`, 2, 0)")) expect_equal(str_sub(x, 1, -2), sql("SUBSTR(`x`, 1, LENGTH(`x`) - 1)")) expect_equal(str_sub(x, 3, -3), sql("SUBSTR(`x`, 3, LENGTH(`x`) - 4)")) expect_equal(str_sub(x, -3, 0), sql("SUBSTR(`x`, LENGTH(`x`) - 2, 0)")) expect_equal(str_sub(x, -3, -3), sql("SUBSTR(`x`, LENGTH(`x`) - 2, 1)")) }) test_that("sql_str_sub can require length paramter", { local_con(simulate_dbi()) x <- ident("x") str_sub <- sql_str_sub("SUBSTR", optional_length = FALSE) expect_equal(str_sub(x), sql("SUBSTR(`x`, 1, LENGTH(`x`))")) expect_equal(str_sub(x, 1), sql("SUBSTR(`x`, 1, LENGTH(`x`))")) expect_equal(str_sub(x, -1), sql("SUBSTR(`x`, LENGTH(`x`), 1)")) }) test_that("str_sub() returns consistent results", { mf <- memdb_frame(t = "abcde") expect_equal(mf %>% transmute(str_sub(t, -3, -1)) %>% pull(1), "cde") expect_equal(mf %>% transmute(str_sub(t, 0, -1)) %>% pull(1), "abcde") expect_equal(mf %>% transmute(str_sub(t, 1, -3)) %>% pull(1), "abc") expect_equal(mf %>% transmute(str_sub(t, -3, 0)) %>% pull(1), "") expect_equal(mf %>% transmute(str_sub(t, 0, 0)) %>% pull(1), "") expect_equal(mf %>% transmute(str_sub(t, 1, 0)) %>% pull(1), "") expect_equal(mf %>% transmute(str_sub(t, -3, 5)) %>% pull(1), "cde") expect_equal(mf %>% transmute(str_sub(t, 0, 1)) %>% pull(1), "a") expect_equal(mf %>% transmute(str_sub(t, 1, 3)) %>% pull(1), "abc") }) dbplyr/tests/testthat/test-verb-select.R0000644000176200001440000001044614004012136020057 0ustar liggesuserstest_that("select quotes correctly", { out <- memdb_frame(x = 1, y = 1) %>% select(x) %>% collect() expect_equal(out, tibble(x = 1)) }) test_that("select can rename", { out <- memdb_frame(x = 1, y = 2) %>% select(y = x) %>% collect() expect_equal(out, tibble(y = 1)) }) test_that("two selects equivalent to one", { mf <- memdb_frame(a = 1, b = 1, c = 1, d = 2) out <- mf %>% select(a:c) %>% select(b:c) %>% collect() expect_named(out, c("b", "c")) }) test_that("select operates on mutated vars", { mf <- memdb_frame(x = c(1, 2, 3), y = c(3, 2, 1)) df1 <- mf %>% mutate(x, z = x + y) %>% select(z) %>% collect() df2 <- mf %>% collect() %>% mutate(x, z = x + y) %>% select(z) expect_equal_tbl(df1, df2) }) test_that("select renames variables (#317)", { mf <- memdb_frame(x = 1, y = 2) expect_equal_tbl(mf %>% select(A = x), tibble(A = 1)) }) test_that("rename renames variables", { mf <- memdb_frame(x = 1, y = 2) expect_equal_tbl(mf %>% rename(A = x), tibble(A = 1, y = 2)) }) test_that("can rename multiple vars", { mf <- memdb_frame(a = 1, b = 2) exp <- tibble(c = 1, d = 2) expect_equal_tbl(mf %>% rename(c = a, d = b), exp) expect_equal_tbl(mf %>% group_by(a) %>% rename(c = a, d = b), exp) }) test_that("can rename with a function", { mf <- memdb_frame(a = 1, b = 2) expect_named(mf %>% rename_with(toupper) %>% collect(), c("A", "B")) expect_named(mf %>% rename_with(toupper, 1) %>% collect(), c("A", "b")) }) test_that("select preserves grouping vars", { mf <- memdb_frame(a = 1, b = 2) %>% group_by(b) expect_snapshot(out <- mf %>% select(a) %>% collect()) expect_named(out, c("b", "a")) }) test_that("select doesn't relocate grouping vars to the front", { mf <- memdb_frame(a = 1, b = 2) %>% group_by(b) expect_equal(mf %>% select(a, b) %>% op_vars(), c("a", "b")) }) test_that("relocate works", { mf <- memdb_frame(a = 1, b = 2, c = 1) %>% group_by(b) out1 <- mf %>% relocate(c) %>% collect() expect_named(out1, c("c", "a", "b")) out2 <- mf %>% relocate(a, .after = c) %>% collect() expect_named(out2, c("b", "c", "a")) }) # sql_render -------------------------------------------------------------- test_that("multiple selects are collapsed", { lf <- lazy_frame(x = 1, y = 2) expect_snapshot(lf %>% select(2:1) %>% select(2:1)) expect_snapshot(lf %>% select(2:1) %>% select(2:1) %>% select(2:1)) expect_snapshot(lf %>% select(x1 = x) %>% select(x2 = x1)) }) test_that("mutate collapses over nested select", { lf <- lazy_frame(g = 0, x = 1, y = 2) expect_snapshot(lf %>% mutate(a = 1, b = 2) %>% select(a)) expect_snapshot(lf %>% mutate(a = 1, b = 2) %>% select(x)) }) # sql_build ------------------------------------------------------------- test_that("select picks variables", { out <- lazy_frame(x1 = 1, x2 = 1, x3 = 2) %>% select(x1:x2) %>% sql_build() expect_equal(out$select, sql("x1" = "`x1`", "x2" = "`x2`")) }) test_that("select renames variables", { out <- lazy_frame(x1 = 1, x2 = 1, x3 = 2) %>% select(y = x1, z = x2) %>% sql_build() expect_equal(out$select, sql("y" = "`x1`", "z" = "`x2`")) }) test_that("select can refer to variables in local env", { vars <- c("x", "y") out <- lazy_frame(x = 1, y = 1) %>% select(dplyr::one_of(vars)) %>% sql_build() expect_equal(out$select, sql("x" = "`x`", "y" = "`y`")) }) test_that("rename preserves existing vars", { out <- lazy_frame(x = 1, y = 1) %>% rename(z = y) %>% sql_build() expect_equal(out$select, sql("x" = "`x`", "z" = "`y`")) }) # ops --------------------------------------------------------------------- test_that("select reduces variables", { out <- mtcars %>% tbl_lazy() %>% select(mpg:disp) expect_equal(op_vars(out), c("mpg", "cyl", "disp")) }) test_that("rename preserves existing", { out <- tibble(x = 1, y = 2) %>% tbl_lazy() %>% rename(z = y) expect_equal(op_vars(out), c("x", "z")) }) test_that("rename renames grouping vars", { df <- lazy_frame(a = 1, b = 2) expect_equal(df %>% group_by(a) %>% rename(c = a) %>% op_grps(), "c") }) test_that("mutate preserves grouping vars (#396)", { df <- lazy_frame(a = 1, b = 2, c = 3) %>% group_by(a, b) expect_equal(df %>% mutate(a = 1) %>% op_grps(), c("a", "b")) expect_equal(df %>% mutate(b = 1) %>% op_grps(), c("a", "b")) }) dbplyr/tests/testthat/test-verb-uncount.R0000644000176200001440000000317714004012136020276 0ustar liggesuserstest_that("symbols weights are dropped in output", { # workaround so that sql snapshot is always the same withr::local_options(list(dbplyr_table_name = 2000)) df <- memdb_frame(x = 1, w = 1) expect_equal(dbplyr_uncount(df, w) %>% collect(), tibble(x = 1)) expect_snapshot(dbplyr_uncount(df, w) %>% show_query()) }) test_that("can request to preserve symbols", { df <- memdb_frame(x = 1, w = 1) expect_equal( dbplyr_uncount(df, w, .remove = FALSE) %>% colnames(), c("x", "w") ) }) test_that("unique identifiers created on request", { df <- memdb_frame(w = 1:3) expect_equal( dbplyr_uncount(df, w, .id = "id") %>% collect(), tibble(id = c(1L, 1:2, 1:3)) ) }) test_that("expands constants and expressions", { df <- memdb_frame(x = 1, w = 2) expect_equal(dbplyr_uncount(df, 2) %>% collect(), collect(df)[c(1, 1), ]) expect_equal(dbplyr_uncount(df, 1 + 1) %>% collect(), collect(df)[c(1, 1), ]) }) test_that("works with groups", { df <- memdb_frame(g = 1, x = 1, w = 1) %>% dplyr::group_by(g) expect_equal(group_vars(dbplyr_uncount(df, w)), "g") }) test_that("grouping variable are removed", { df <- memdb_frame(g = 1, x = 1, w = 1) %>% dplyr::group_by(g) expect_equal(dbplyr_uncount(df, g) %>% colnames(), c("x", "w")) }) test_that("must evaluate to integer", { df <- memdb_frame(x = 1, w = 1/2) expect_error(dbplyr_uncount(df, w), class = "vctrs_error_cast_lossy") expect_error(dbplyr_uncount(df, "W"), class = "vctrs_error_incompatible_type") }) test_that("works with 0 weights", { df <- memdb_frame(x = 1:2, w = c(0, 1)) expect_equal(dbplyr_uncount(df, w) %>% collect(), tibble(x = 2)) }) dbplyr/tests/testthat/test-backend-hive.R0000644000176200001440000000127314015731174020176 0ustar liggesuserstest_that("custom scalar & string functions translated correctly", { local_con(simulate_hive()) expect_equal(translate_sql(bitwShiftL(x, 2L)), sql("SHIFTLEFT(`x`, 2)")) expect_equal(translate_sql(bitwShiftR(x, 2L)), sql("SHIFTRIGHT(`x`, 2)")) expect_equal(translate_sql(cot(x)), sql("1.0 / TAN(`x`)")) expect_equal(translate_sql(str_replace_all(x, "old", "new")), sql("REGEXP_REPLACE(`x`, 'old', 'new')")) expect_equal(translate_sql(median(x, na.rm = TRUE)), sql("PERCENTILE(`x`, 0.5) OVER ()")) }) test_that("generates custom sql", { expect_snapshot(sql_table_analyze(simulate_hive(), in_schema("schema", "tbl"))) }) dbplyr/tests/testthat/test-backend-postgres-old.R0000644000176200001440000000105114004012136021643 0ustar liggesuserstest_that("RPostgreSQL backend", { skip_if_not(identical(Sys.getenv("GITHUB_POSTGRES"), "true")) src <- DBI::dbConnect( RPostgreSQL::PostgreSQL(), dbname = "test", user = "postgres", password = "password", host = "127.0.0.1" ) copy_to(src, mtcars, "mtcars", overwrite = TRUE, temporary = FALSE) withr::defer(DBI::dbRemoveTable(src, "mtcars")) expect_identical(colnames(tbl(src, "mtcars")), colnames(mtcars)) src_cyl <- tbl(src, "mtcars") %>% select(cyl) %>% collect() expect_identical(src_cyl$cyl, mtcars$cyl) }) dbplyr/tests/testthat/test-query-join.R0000644000176200001440000000221314002647450017752 0ustar liggesuserstest_that("print method doesn't change unexpectedly", { lf1 <- lazy_frame(x = 1, y = 2) lf2 <- lazy_frame(x = 1, z = 2) expect_snapshot(sql_build(left_join(lf1, lf2))) }) test_that("generated sql doesn't change unexpectedly", { lf <- lazy_frame(x = 1, y = 2) expect_snapshot(inner_join(lf, lf)) expect_snapshot(left_join(lf, lf)) expect_snapshot(right_join(lf, lf)) expect_snapshot(full_join(lf, lf)) }) test_that("only disambiguates shared variables", { lf1 <- lazy_frame(x = 1, y = 2) lf2 <- lazy_frame(x = 1, z = 2) expect_snapshot(left_join(lf1, lf2)) expect_snapshot(left_join(lf1, lf2, by = c("y" = "z"))) }) test_that("sql_on query doesn't change unexpectedly", { lf1 <- lazy_frame(x = 1, y = 2) lf2 <- lazy_frame(x = 1, z = 3) expect_snapshot(inner_join(lf1, lf2, sql_on = "LHS.y < RHS.z")) expect_snapshot(left_join(lf1, lf2, sql_on = "LHS.y < RHS.z")) expect_snapshot(right_join(lf1, lf2, sql_on = "LHS.y < RHS.z")) expect_snapshot(full_join(lf1, lf2, sql_on = "LHS.y < RHS.z")) expect_snapshot(semi_join(lf1, lf2, sql_on = "LHS.y < RHS.z")) expect_snapshot(anti_join(lf1, lf2, sql_on = "LHS.y < RHS.z")) }) dbplyr/tests/testthat/test-translate-sql-paste.R0000644000176200001440000000123414002647450021556 0ustar liggesuserstest_that("basic prefix operation", { local_con(simulate_dbi()) paste <- sql_paste("") x <- ident("x") y <- ident("y") expect_equal(paste(x), sql("CONCAT_WS('', `x`)")) expect_equal(paste(x, y), sql("CONCAT_WS('', `x`, `y`)")) expect_equal(paste(x, y, sep = " "), sql("CONCAT_WS(' ', `x`, `y`)")) }) test_that("basic infix operation", { local_con(simulate_dbi()) paste <- sql_paste_infix("", "&&", function(x) sql_expr(cast((!!x) %as% text))) x <- ident("x") y <- ident("y") expect_equal(paste(x), sql("CAST(`x` AS text)")) expect_equal(paste(x, y), sql("`x` && `y`")) expect_equal(paste(x, y, sep = " "), sql("`x` && ' ' && `y`")) }) dbplyr/tests/testthat/test-backend-.R0000644000176200001440000000577614015731174017336 0ustar liggesusers# mathematics -------------------------------------------------------- test_that("basic arithmetic is correct", { expect_equal(translate_sql(1 + 2), sql("1.0 + 2.0")) expect_equal(translate_sql(2 * 4), sql("2.0 * 4.0")) expect_equal(translate_sql(5 ^ 2), sql("POWER(5.0, 2.0)")) expect_equal(translate_sql(100L %% 3L), sql("100 % 3")) expect_error(translate_sql(100L %/% 3L), "not available") }) test_that("small numbers aren't converted to 0", { expect_equal(translate_sql(1e-9), sql("1e-09")) }) test_that("unary minus flips sign of number", { expect_equal(translate_sql(-10L), sql("-10")) expect_equal(translate_sql(x == -10), sql('`x` = -10.0')) expect_equal(translate_sql(x %in% c(-1L, 0L)), sql('`x` IN (-1, 0)')) }) test_that("unary minus wraps non-numeric expressions", { expect_equal(translate_sql(-(1L + 2L)), sql("-(1 + 2)")) expect_equal(translate_sql(-mean(x, na.rm = TRUE), window = FALSE), sql('-AVG(`x`)')) }) test_that("binary minus subtracts", { expect_equal(translate_sql(1L - 10L), sql("1 - 10")) }) test_that("log base comes first", { expect_equal(translate_sql(log(x, 10)), sql('LOG(10.0, `x`)')) }) test_that("log becomes ln", { expect_equal(translate_sql(log(x)), sql('LN(`x`)')) }) test_that("can translate subsetting", { expect_equal(translate_sql(a$b), sql("`a`.`b`")) expect_equal(translate_sql(a[["b"]]), sql("`a`.`b`")) expect_equal(translate_sql(a[["b"]][[1]]), sql('`a`.`b`[1]')) }) # binary/bitwise --------------------------------------------------------------- test_that("bitwise operations", { expect_equal(translate_sql(bitwNot(x)), sql("~(`x`)")) expect_equal(translate_sql(bitwAnd(x, 128L)), sql("`x` & 128")) expect_equal(translate_sql(bitwOr(x, 128L)), sql("`x` | 128")) expect_equal(translate_sql(bitwXor(x, 128L)), sql("`x` ^ 128")) expect_equal(translate_sql(bitwShiftL(x, 2L)), sql("`x` << 2")) expect_equal(translate_sql(bitwShiftR(x, 2L)), sql("`x` >> 2")) }) test_that("default raw escapes translated correctly", { mf <- lazy_frame(x = "abc", con = simulate_sqlite()) a <- blob::as_blob("abc") b <- blob::as_blob(as.raw(c(0x01, 0x02))) L <- c(a, b) expect_snapshot(mf %>% filter(x == a)) expect_snapshot(mf %>% filter(x %in% L)) qry <- mf %>% filter(x %in% !!L) expect_snapshot(qry) }) # DDL --------------------------------------------------------------------- test_that("DDL operations generate expected SQL", { con <- simulate_dbi() expect_snapshot(sql_table_analyze(con, in_schema("schema", "tbl"))) expect_snapshot(sql_query_explain(con, sql("SELECT * FROM foo"))) expect_snapshot(sql_query_wrap(con, ident("table"))) expect_snapshot(sql_query_wrap(con, in_schema("schema", "tbl"))) expect_snapshot(sql_query_wrap(con, sql("SELECT * FROM foo"))) expect_snapshot(sql_table_index(con, in_schema("schema", "tbl"), c("a", "b"))) expect_snapshot(sql_table_index(con, in_schema("schema", "tbl"), "c", unique = TRUE)) expect_snapshot(sql_query_save(con, sql("SELECT * FROM foo"), in_schema("temp", "tbl"))) }) dbplyr/tests/testthat/test-backend-mysql.R0000644000176200001440000000216114015731174020405 0ustar liggesuserstest_that("use CHAR type for as.character", { local_con(simulate_mysql()) expect_equal(translate_sql(as.character(x)), sql("CAST(`x` AS CHAR)")) }) test_that("generates custom sql", { con <- simulate_mysql() expect_snapshot(sql_table_analyze(con, in_schema("schema", "tbl"))) expect_snapshot(sql_query_explain(con, sql("SELECT * FROM table"))) lf <- lazy_frame(x = 1, con = con) expect_snapshot(left_join(lf, lf, by = "x", na_matches = "na")) }) # live database ----------------------------------------------------------- test_that("logicals converted to integer correctly", { db <- copy_to_test("MariaDB", data.frame(x = c(TRUE, FALSE, NA))) expect_identical(db %>% pull(), c(1L, 0L, NA)) }) test_that("can explain", { db <- copy_to_test("MariaDB", data.frame(x = 1:3)) expect_snapshot(db %>% mutate(y = x + 1) %>% explain()) }) test_that("can overwrite temp tables", { con <- src_test("MariaDB") df1 <- tibble(x = 1) copy_to(con, df1, "test-df", temporary = TRUE) df2 <- tibble(x = 2) db2 <- copy_to(con, df2, "test-df", temporary = TRUE, overwrite = TRUE) expect_equal(collect(db2), df2) }) dbplyr/tests/testthat/test-verb-fill.R0000644000176200001440000000373514004012136017531 0ustar liggesusersdf <- tibble::tribble( ~ id, ~group, ~letter, ~n1, ~ n2, 1, 1, NA, NA, 1, 2, 1, "a", 2, NA, 3, 1, NA, NA, NA, 4, 1, "a", NA, 4, 5, 2, "a", 5, NA, 6, 2, NA, NA, 6, ) df_db <- copy_to(src_memdb(), df, name = "df", overwrite = TRUE) df <- tibble::tribble( ~ id, ~group, ~n1, 1, 1, NA, 2, 1, 2, 3, 1, NA, 4, 1, NA, 5, 2, 5, 6, 2, NA, ) df_lazy_ns <- tbl_lazy(df, con = simulate_sqlite()) df_lazy_std <- tbl_lazy(df) test_that("fill works", { expect_equal( df_db %>% window_order(id) %>% group_by(group) %>% tidyr::fill(n1, n2) %>% collect(), tibble::tribble( ~ id, ~group, ~letter, ~n1, ~ n2, 1, 1, NA, NA, 1, 2, 1, "a", 2, 1, 3, 1, NA, 2, 1, 4, 1, "a", 2, 4, 5, 2, "a", 5, NA, 6, 2, NA, 5, 6, ) %>% group_by(group) ) }) test_that("up-direction works", { expect_snapshot( df_lazy_ns %>% window_order(id) %>% tidyr::fill(n1, .direction = "up") ) expect_snapshot( df_lazy_std %>% window_order(id) %>% tidyr::fill(n1, .direction = "up") ) }) test_that("up-direction works with descending", { expect_snapshot( df_lazy_ns %>% window_order(desc(id)) %>% tidyr::fill(n1, .direction = "up") ) expect_snapshot( df_lazy_std %>% window_order(desc(id)) %>% tidyr::fill(n1, .direction = "up") ) }) test_that("groups are respected", { expect_snapshot( group_by(df_lazy_ns, group) %>% window_order(id) %>% tidyr::fill(n1) ) expect_snapshot( group_by(df_lazy_std, group) %>% window_order(id) %>% tidyr::fill(n1) ) }) test_that("fill errors on unsorted data", { expect_snapshot({ expect_error(df_db %>% tidyr::fill(n1)) }) }) dbplyr/tests/testthat/test-sql.R0000644000176200001440000000031414002647450016447 0ustar liggesuserstest_that("can concatenate sql vector without supplying connection", { expect_equal(c(sql("x")), sql("x")) expect_equal(c(sql("x"), "x"), sql("x", "'x'")) expect_equal(c(ident("x")), sql("`x`")) }) dbplyr/tests/testthat/test-utils.R0000644000176200001440000000160214002647450017011 0ustar liggesuserstest_that("deparse_trunc() expression to text", { expect_equal( deparse_trunc(expr(test)), "test" ) dt <- deparse_trunc( expr(!!paste0(rep("x", 200), collapse = "")) ) expect_equal( nchar(dt), getOption("width") ) }) test_that("Says 1.1 is not a whole number", { expect_false(is.wholenumber(1.1)) }) test_that("Succesful and not-sucessful commands are identified", { expect_true(succeeds("success")) expect_false(succeeds(x - 1, quiet = TRUE)) }) test_that("Dots are collapsed into a single variable", { expect_equal( named_commas(x = 1, y = 2), "x = 1, y = 2" ) expect_equal( named_commas(1, 2), "1, 2" ) }) test_that("Correctly identifies the Travis flag", { expect_equal( in_travis(), Sys.getenv("TRAVIS") == "true" ) }) test_that("Returns error if no characters are passed", { expect_error(c_character(1, 2)) }) dbplyr/tests/testthat/test-verb-joins.R0000644000176200001440000001576014002647450017741 0ustar liggesuserstest_that("complete join pipeline works with SQLite", { df1 <- memdb_frame(x = 1:5) df2 <- memdb_frame(x = c(1, 3, 5), y = c("a", "b", "c")) out <- collect(left_join(df1, df2, by = "x")) expect_equal(out, tibble(x = 1:5, y = c("a", NA, "b", NA, "c"))) }) test_that("complete semi join works with SQLite", { lf1 <- memdb_frame(x = c(1, 2), y = c(2, 3)) lf2 <- memdb_frame(x = 1) lf3 <- inner_join(lf1, lf2, by = "x") expect_equal(op_vars(lf3), c("x", "y")) out <- collect(lf3) expect_equal(out, tibble(x = 1, y = 2)) }) test_that("joins with non by variables gives cross join", { df1 <- memdb_frame(x = 1:5) df2 <- memdb_frame(y = 1:5) out <- collect(inner_join(df1, df2, by = character())) expect_equal(nrow(out), 25) # full_join() goes through a slightly different path and # generates CROSS JOIN for reasons I don't fully understand out <- collect(full_join(df1, df2, by = character())) expect_equal(nrow(out), 25) }) df1 <- memdb_frame(x = 1:5, y = 1:5) df2 <- memdb_frame(a = 5:1, b = 1:5) df3 <- memdb_frame(x = 1:5, z = 1:5) df4 <- memdb_frame(a = 5:1, z = 5:1) test_that("named by join by different x and y vars", { j1 <- collect(inner_join(df1, df2, c("x" = "a"))) expect_equal(names(j1), c("x", "y", "b")) expect_equal(nrow(j1), 5) j2 <- collect(inner_join(df1, df2, c("x" = "a", "y" = "b"))) expect_equal(names(j2), c("x", "y")) expect_equal(nrow(j2), 1) }) test_that("named by join by same z vars", { j1 <- collect(inner_join(df3, df4, c("z" = "z"))) expect_equal(nrow(j1), 5) expect_equal(names(j1), c("x", "z", "a")) }) test_that("join with both same and different vars", { j1 <- collect(left_join(df1, df3, by = c("y" = "z", "x"))) expect_equal(nrow(j1), 5) expect_equal(names(j1), c("x", "y")) }) test_that("joining over arbitrary predicates", { j1 <- collect(left_join(df1, df2, sql_on = "LHS.x = RHS.b")) j2 <- collect(left_join(df1, df2, by = c("x" = "b"))) %>% mutate(b = x) expect_equal(j1, j2) j1 <- collect(left_join(df1, df3, sql_on = "LHS.x = RHS.z")) j2 <- collect(left_join(df1, df3, by = c("x" = "z"))) %>% mutate(z = x.x) expect_equal(j1, j2) j1 <- collect(left_join(df1, df3, sql_on = "LHS.x = RHS.x")) j2 <- collect(left_join(df1, df3, by = "x")) %>% mutate(x.y = x) %>% select(x.x = x, y, x.y, z) expect_equal(j1, j2) }) test_that("inner join doesn't result in duplicated columns ", { expect_equal(colnames(inner_join(df1, df1, by = c("x", "y"))), c("x", "y")) }) test_that("self-joins allowed with named by", { fam <- memdb_frame(id = 1:5, parent = c(NA, 1, 2, 2, 4)) j1 <- fam %>% left_join(fam, by = c("parent" = "id")) j2 <- fam %>% inner_join(fam, by = c("parent" = "id")) expect_equal(op_vars(j1), c("id", "parent.x", "parent.y")) expect_equal(op_vars(j2), c("id", "parent.x", "parent.y")) expect_equal(nrow(collect(j1)), 5) expect_equal(nrow(collect(j2)), 4) j3 <- collect(semi_join(fam, fam, by = c("parent" = "id"))) j4 <- collect(anti_join(fam, fam, by = c("parent" = "id"))) expect_equal(j3, filter(collect(fam), !is.na(parent))) expect_equal(j4, filter(collect(fam), is.na(parent))) }) test_that("suffix modifies duplicated variable names", { fam <- memdb_frame(id = 1:5, parent = c(NA, 1, 2, 2, 4)) j1 <- collect(inner_join(fam, fam, by = c("parent" = "id"), suffix = c("1", "2"))) j2 <- collect(left_join(fam, fam, by = c("parent" = "id"), suffix = c("1", "2"))) expect_named(j1, c("id", "parent1", "parent2")) expect_named(j2, c("id", "parent1", "parent2")) }) test_that("join variables always disambiguated (#2823)", { # Even if the new variable conflicts with an existing variable df1 <- dbplyr::memdb_frame(a = 1, b.x = 1, b = 1) df2 <- dbplyr::memdb_frame(a = 1, b = 1) both <- collect(left_join(df1, df2, by = "a")) expect_named(both, c("a", "b.x", "b.x.x", "b.y")) }) test_that("join functions error on column not found for SQL sources #1928", { # Rely on dplyr to test precise code expect_error( left_join(memdb_frame(x = 1:5), memdb_frame(y = 1:5), by = "x"), "missing|(not found)" ) expect_error( left_join(memdb_frame(x = 1:5), memdb_frame(y = 1:5), by = "y"), "missing|(not found)" ) expect_error( left_join(memdb_frame(x = 1:5), memdb_frame(y = 1:5)), "[Nn]o common variables" ) }) # sql_build --------------------------------------------------------------- test_that("join verbs generate expected ops", { lf1 <- lazy_frame(x = 1, y = 2) lf2 <- lazy_frame(x = 1, z = 2) ji <- inner_join(lf1, lf2, by = "x") expect_s3_class(ji$ops, "op_join") expect_equal(ji$ops$args$type, "inner") jl <- left_join(lf1, lf2, by = "x") expect_s3_class(jl$ops, "op_join") expect_equal(jl$ops$args$type, "left") jr <- right_join(lf1, lf2, by = "x") expect_s3_class(jr$ops, "op_join") expect_equal(jr$ops$args$type, "right") jf <- full_join(lf1, lf2, by = "x") expect_s3_class(jf$ops, "op_join") expect_equal(jf$ops$args$type, "full") js <- semi_join(lf1, lf2, by = "x") expect_s3_class(js$ops, "op_semi_join") expect_equal(js$ops$args$anti, FALSE) ja <- anti_join(lf1, lf2, by = "x") expect_s3_class(ja$ops, "op_semi_join") expect_equal(ja$ops$args$anti, TRUE) }) test_that("can optionally match NA values", { lf <- lazy_frame(x = 1) expect_snapshot(left_join(lf, lf, by = "x", na_matches = "na")) }) test_that("join captures both tables", { lf1 <- lazy_frame(x = 1, y = 2) lf2 <- lazy_frame(x = 1, z = 2) out <- inner_join(lf1, lf2, by = "x") %>% sql_build() expect_s3_class(out, "join_query") expect_equal(op_vars(out$x), c("x", "y")) expect_equal(op_vars(out$y), c("x", "z")) expect_equal(out$type, "inner") }) test_that("semi join captures both tables", { lf1 <- lazy_frame(x = 1, y = 2) lf2 <- lazy_frame(x = 1, z = 2) out <- semi_join(lf1, lf2, by = "x") %>% sql_build() expect_equal(op_vars(out$x), c("x", "y")) expect_equal(op_vars(out$y), c("x", "z")) expect_equal(out$anti, FALSE) }) test_that("set ops captures both tables", { lf1 <- lazy_frame(x = 1, y = 2) lf2 <- lazy_frame(x = 1, z = 2) out <- union(lf1, lf2) %>% sql_build() expect_equal(out$type, "UNION") }) test_that("extra args generates error", { lf1 <- lazy_frame(x = 1, y = 2) lf2 <- lazy_frame(x = 1, z = 2) expect_error( inner_join(lf1, lf2, by = "x", never_used = "na"), "unused argument" ) }) # ops --------------------------------------------------------------------- test_that("joins get vars from both left and right", { out <- left_join( lazy_frame(x = 1, y = 1), lazy_frame(x = 2, z = 2), by = "x" ) expect_equal(op_vars(out), c("x", "y", "z")) }) test_that("semi joins get vars from left", { out <- semi_join( lazy_frame(x = 1, y = 1), lazy_frame(x = 2, z = 2), by = "x" ) expect_equal(op_vars(out), c("x", "y")) }) # Helpers ----------------------------------------------------------------- test_that("add_suffixes works if no suffix requested", { expect_equal(add_suffixes(c("x", "x"), "y", ""), c("x", "x")) expect_equal(add_suffixes(c("x", "y"), "y", ""), c("x", "y")) }) dbplyr/tests/testthat/test-verb-head.R0000644000176200001440000000163414002647450017513 0ustar liggesuserstest_that("head limits rows", { db <- head(memdb_frame(x = 1:100), 10) expect_equal(sql_build(db)$limit, 10) expect_equal(nrow(collect(db)), 10) }) test_that("two heads are equivalent to one", { out <- lazy_frame(x = 1:10) %>% head(3) %>% head(5) expect_equal(out$ops$args$n, 3) }) test_that("non-integer automatically truncated", { out <- lazy_frame(x = 1:10) %>% head(3.5) expect_equal(out$ops$args$n, 3) }) test_that("can get 0 rows", { db <- memdb_frame(x = 1) out <- collect(head(db, 0)) expect_equal(out, tibble(x = double())) }) test_that("n must be valid", { db <- memdb_frame(x = 1) expect_error(head(db, "x"), "non-negative integer") expect_error(head(db, 1:2), "non-negative integer") expect_error(head(db, -1), "non-negative integer") expect_error(head(db, Inf), NA) }) test_that("tail not supported", { lf <- lazy_frame(x = 1) expect_error(tail(lf), "not supported") }) dbplyr/tests/testthat/test-test-backend-snowflake.R0000644000176200001440000000022614031447261022205 0ustar liggesuserstest_that("custom scalar translated correctly", { local_con(simulate_snowflake()) expect_equal(translate_sql(log10(x)), sql("LOG(10.0, `x`)")) }) dbplyr/tests/testthat/test-tbl-lazy.R0000644000176200001440000000131414002647450017407 0ustar liggesuserstest_that("adds src class", { tb <- tbl_lazy(mtcars, con = simulate_sqlite()) expect_s3_class(tb, "tbl_SQLiteConnection") }) test_that("has print method", { expect_snapshot(tbl_lazy(mtcars)) }) test_that("support colwise variants", { mf <- memdb_frame(x = 1:5, y = factor(letters[1:5])) exp <- mf %>% collect() %>% mutate(y = as.character(y)) expect_message( mf1 <- dplyr::mutate_if(mf, is.factor, as.character), "on the first 100 rows" ) expect_equal_tbl(mf1, exp) mf2 <- dplyr::mutate_at(mf, "y", as.character) expect_equal_tbl(mf2, exp) }) test_that("base source of tbl_lazy is always 'df'", { out <- lazy_frame(x = 1, y = 5) %>% sql_build() expect_equal(out, ident("df")) }) dbplyr/tests/testthat/test-verb-set-ops.R0000644000176200001440000000410014002647450020173 0ustar liggesuserstest_that("column order is matched", { df1 <- memdb_frame(x = 1, y = 2) df2 <- memdb_frame(y = 1, x = 2) out <- collect(union(df1, df2)) expect_equal(out, tibble(x = c(1, 2), y = c(2, 1))) }) test_that("missing columns filled with NULL", { df1 <- memdb_frame(x = 1) df2 <- memdb_frame(y = 2) out <- collect(union_all(df1, df2)) expect_equal(out, tibble(x = c(1, NA), y = c(NA, 2))) }) # SQL generation ---------------------------------------------------------- test_that("set ops generates correct sql", { lf1 <- memdb_frame(x = 1) lf2 <- memdb_frame(x = c(1, 2)) out <- lf1 %>% union(lf2) %>% collect() expect_equal(out, tibble(x = c(1, 2))) }) test_that("union and union all work for all backends", { df <- tibble(x = 1:10, y = x %% 2) tbls_full <- test_load(df) tbls_filter <- test_load(filter(df, y == 0)) tbls_full %>% purrr::map2(tbls_filter, union) %>% expect_equal_tbls() tbls_full %>% purrr::map2(tbls_filter, union_all) %>% expect_equal_tbls() }) test_that("intersect and setdiff work for supported backends", { df <- tibble(x = 1:10, y = x %% 2) # MySQL doesn't support EXCEPT or INTERSECT tbls_full <- test_load(df, ignore = c("mysql", "MariaDB")) tbls_filter <- test_load(filter(df, y == 0), ignore = c("mysql", "MariaDB")) tbls_full %>% purrr::map2(tbls_filter, intersect) %>% expect_equal_tbls() tbls_full %>% purrr::map2(tbls_filter, setdiff) %>% expect_equal_tbls() }) test_that("SQLite warns if set op attempted when tbl has LIMIT", { mf <- memdb_frame(x = 1:2) m1 <- head(mf, 1) expect_error(union(mf, m1), "does not support") expect_error(union(m1, mf), "does not support") }) test_that("other backends can combine with a limit", { df <- tibble(x = 1:2) # sqlite only allows limit at top level tbls_full <- test_load(df, ignore = "sqlite") tbls_head <- lapply(test_load(df, ignore = "sqlite"), head, n = 1) tbls_full %>% purrr::map2(tbls_head, union) %>% expect_equal_tbls() tbls_full %>% purrr::map2(tbls_head, union_all) %>% expect_equal_tbls() }) dbplyr/tests/testthat/test-verb-slice.R0000644000176200001440000000374314002647450017714 0ustar liggesuserstest_that("slice, head and tail aren't available", { lf <- lazy_frame(x = 1) expect_snapshot_error(lf %>% slice()) expect_snapshot_error(lf %>% slice_head()) expect_snapshot_error(lf %>% slice_tail()) }) test_that("slice_min handles arguments", { db <- memdb_frame(x = c(1, 1, 2), id = c(1, 2, 3)) expect_equal(db %>% slice_min(id) %>% pull(), 1) expect_equal(db %>% slice_min(x) %>% pull(), c(1, 2)) expect_equal(db %>% slice_min(x, with_ties = FALSE) %>% pull(), 1) expect_equal(db %>% slice_min(id, n = 2) %>% pull(), c(1, 2)) expect_equal(db %>% slice_min(id, prop = 0.5) %>% pull(), 1) expect_snapshot_error(db %>% slice_min()) expect_snapshot_error(db %>% slice_min(id, prop = 0.5, with_ties = FALSE)) }) test_that("slice_max orders in opposite order", { db <- memdb_frame(x = c(1, 1, 2), id = c(1, 2, 3)) expect_equal(db %>% slice_max(id) %>% pull(), 3) expect_equal(db %>% slice_max(x) %>% pull(), 3) expect_snapshot_error(db %>% slice_max()) }) test_that("slice_sample errors when expected", { db <- memdb_frame(x = c(1, 1, 2), id = c(1, 2, 3)) # Can't see how to test this, but interactive experimentation # shows that it doesn't always return the same result expect_error(db %>% slice_sample() %>% pull(), NA) expect_snapshot_error(db %>% slice_sample(replace = TRUE)) expect_snapshot_error(db %>% slice_sample(weight_by = x)) expect_snapshot_error(db %>% slice_sample(prop = 0.5)) }) test_that("window_order is preserved", { db <- memdb_frame(x = c(1, 1, 2), id = c(1, 2, 3)) sort <- db %>% window_order(x) %>% slice_min(id) %>% op_sort() expect_equal(length(sort), 1) expect_equal(get_expr(sort[[1]]), quote(x)) }) test_that("check_slice_size checks for common issues", { expect_snapshot_error(check_slice_size(n = 1, prop = 1)) expect_snapshot_error(check_slice_size(n = "a")) expect_snapshot_error(check_slice_size(prop = "a")) expect_snapshot_error(check_slice_size(n = -1)) expect_snapshot_error(check_slice_size(prop = -1)) }) dbplyr/tests/testthat/test-query-select.R0000644000176200001440000000324314002647450020276 0ustar liggesuserstest_that("select_query() print method output is as expected", { mf <- select_query(lazy_frame(x = 1, con = simulate_dbi())) expect_snapshot(mf) }) test_that("queries generated by select() don't alias unnecessarily", { lf_build <- lazy_frame(x = 1) %>% select(x) %>% sql_build() lf_render <- sql_render(lf_build, con = simulate_dbi()) expect_snapshot(lf_render) }) # Optimisations ----------------------------------------------------------- test_that("optimisation is turned on by default", { lf <- lazy_frame(x = 1, y = 2) %>% arrange(x) %>% head(5) qry <- lf %>% sql_build() expect_equal(qry$from, ident("df")) }) test_that("group by then limit is collapsed", { lf <- memdb_frame(x = 1:10, y = 2) %>% group_by(x) %>% summarise(y = sum(y, na.rm = TRUE)) %>% head(1) qry <- lf %>% sql_build() expect_equal(qry$limit, 1L) expect_equal(qry$group_by, sql('`x`')) # And check that it returns the correct value expect_equal(collect(lf), tibble(x = 1L, y = 2)) }) test_that("filter and rename are correctly composed", { lf <- memdb_frame(x = 1, y = 2) %>% filter(x == 1) %>% select(x = y) qry <- lf %>% sql_build() expect_equal(qry$select, sql(x = "`y`")) expect_equal(qry$where, sql('`x` = 1.0')) # It surprises me that this SQL works! expect_equal(collect(lf), tibble(x = 2)) }) test_that("trivial subqueries are collapsed", { lf <- memdb_frame(a = 1:3) %>% mutate(b = a + 1) %>% distinct() %>% arrange() qry <- lf %>% sql_build() expect_s3_class(qry$from, "ident") expect_true(qry$distinct) # And check that it returns the correct value expect_equal(collect(lf), tibble(a = 1:3, b = a + 1.0)) }) dbplyr/tests/testthat/test-translate-sql-conditional.R0000644000176200001440000000076414002647450022754 0ustar liggesuserstest_that("case_when converted to CASE WHEN", { expect_snapshot(translate_sql(case_when(x > 1L ~ "a"))) }) test_that("even inside mutate", { out <- lazy_frame(x = 1:5) %>% mutate(y = case_when(x > 1L ~ "a")) %>% sql_build() expect_snapshot(out$select[[2]]) }) test_that("case_when translates correctly to ELSE when TRUE ~ is used 2", { expect_snapshot( translate_sql( case_when( x == 1L ~ "yes", x == 0L ~ "no", TRUE ~ "undefined") ) ) }) dbplyr/tests/testthat/test-backend-redshift.R0000644000176200001440000000247114004012136021040 0ustar liggesuserstest_that("defaults to postgres translations", { local_con(simulate_redshift()) expect_equal(translate_sql(log10(x)), sql("LOG(`x`)")) }) test_that("string translations", { local_con(simulate_redshift()) expect_error(translate_sql(str_replace("xx", ".", "a")), "not available") expect_equal(translate_sql(str_replace_all("xx", ".", "a")), sql("REGEXP_REPLACE('xx', '.', 'a')")) expect_equal(translate_sql(substr(x, 2, 2)), sql("SUBSTRING(`x`, 2, 1)")) expect_equal(translate_sql(str_sub(x, 2, -2)), sql("SUBSTRING(`x`, 2, LEN(`x`) - 2)")) expect_equal(translate_sql(paste("x", "y")), sql("'x' || ' ' || 'y'")) expect_equal(translate_sql(paste0("x", "y")), sql("'x' || 'y'")) expect_equal(translate_sql(str_c("x", "y")), sql("'x' || 'y'")) }) test_that("numeric translations", { local_con(simulate_redshift()) expect_equal(translate_sql(as.numeric(x)), sql("CAST(`x` AS FLOAT)")) expect_equal(translate_sql(as.double(x)), sql("CAST(`x` AS FLOAT)")) }) test_that("lag and lead translation", { local_con(simulate_redshift()) expect_equal(translate_sql(lead(x)), sql("LEAD(`x`, 1) OVER ()")) expect_equal(translate_sql(lag(x)), sql("LAG(`x`, 1) OVER ()")) expect_error(translate_sql(lead(x, default = y)), "unused argument") expect_error(translate_sql(lag(x, default = y)), "unused argument") }) dbplyr/tests/testthat/test-verb-do.R0000644000176200001440000000237114004012136017200 0ustar liggesuserstest_that("ungrouped data collected first", { out <- memdb_frame(x = 1:2) %>% do(head(.)) expect_equal(out, tibble(x = 1:2)) }) test_that("named argument become list columns", { mf <- memdb_frame( g = rep(1:3, 1:3), x = 1:6 ) %>% group_by(g) out <- mf %>% do(nrow = nrow(.), ncol = ncol(.)) expect_equal(out$nrow, list(1, 2, 3)) expect_equal(out$ncol, list(2, 2, 2)) }) test_that("unnamed results bound together by row", { mf <- memdb_frame( g = c(1, 1, 2, 2), x = c(3, 9, 4, 9) ) %>% group_by(g) first <- mf %>% do(head(., 1)) expect_equal_tbl(first, tibble(g = c(1, 2), x = c(3, 4))) }) test_that("Results respect select", { mf <- memdb_frame( g = c(1, 1, 2, 2), x = c(3, 9, 4, 9), y = 1:4, z = 4:1 ) %>% group_by(g) expect_message(out <- mf %>% select(x) %>% do(ncol = ncol(.))) expect_equal(out$g, c(1, 2)) expect_equal(out$ncol, list(2L, 2L)) }) test_that("results independent of chunk_size", { mf <- memdb_frame( g = rep(1:3, 1:3), x = 1:6 ) %>% group_by(g) nrows <- function(group, n) { unlist(do(group, nrow = nrow(.), .chunk_size = n)$nrow) } expect_equal(nrows(mf, 1), c(1, 2, 3)) expect_equal(nrows(mf, 2), c(1, 2, 3)) expect_equal(nrows(mf, 10), c(1, 2, 3)) }) dbplyr/tests/testthat/test-query-semi-join.R0000644000176200001440000000053114002647450020706 0ustar liggesuserstest_that("print method doesn't change unexpectedly", { lf1 <- lazy_frame(x = 1, y = 2) lf2 <- lazy_frame(x = 1, z = 2) expect_snapshot(sql_build(semi_join(lf1, lf2))) }) test_that("generated sql doesn't change unexpectedly", { lf <- lazy_frame(x = 1, y = 2) expect_snapshot(semi_join(lf, lf)) expect_snapshot(anti_join(lf, lf)) }) dbplyr/tests/testthat/test-backend-access.R0000644000176200001440000000633014031447261020502 0ustar liggesuserstest_that("custom scalar translated correctly", { local_con(simulate_access()) # Conversion expect_equal(translate_sql(as.numeric(x)), sql("CDBL(`x`)")) expect_equal(translate_sql(as.double(x)), sql("CDBL(`x`)")) expect_equal(translate_sql(as.integer(x)), sql("INT(`x`)")) expect_equal(translate_sql(as.logical(x)), sql("CBOOL(`x`)")) expect_equal(translate_sql(as.character(x)), sql("CSTR(`x`)")) expect_equal(translate_sql(as.Date(x)), sql("CDATE(`x`)")) # Math expect_equal(translate_sql(exp(x)), sql("EXP(`x`)")) expect_equal(translate_sql(log(x)), sql("LOG(`x`)")) expect_equal(translate_sql(log10(x)), sql("LOG(`x`) / LOG(10)")) expect_equal(translate_sql(sqrt(x)), sql("SQR(`x`)")) expect_equal(translate_sql(sign(x)), sql("SGN(`x`)")) expect_equal(translate_sql(floor(x)), sql("INT(`x`)")) expect_equal(translate_sql(ceiling(x)), sql("INT(`x` + 0.9999999999)")) expect_equal(translate_sql(ceil(x)), sql("INT(`x` + 0.9999999999)")) # String expect_equal(translate_sql(nchar(x)), sql("LEN(`x`)")) expect_equal(translate_sql(tolower(x)), sql("LCASE(`x`)")) expect_equal(translate_sql(toupper(x)), sql("UCASE(`x`)")) expect_equal(translate_sql(substr(x, 1, 2)), sql("RIGHT(LEFT(`x`, 2.0), 2.0)")) expect_equal(translate_sql(paste(x)), sql("CSTR(`x`)")) expect_equal(translate_sql(trimws(x)), sql("TRIM(`x`)")) expect_equal(translate_sql(is.null(x)), sql("ISNULL(`x`)")) expect_equal(translate_sql(is.na(x)), sql("ISNULL(`x`)")) expect_equal(translate_sql(coalesce(x, y)), sql("IIF(ISNULL(`x`), `y`, `x`)")) expect_equal(translate_sql(pmin(x, y)), sql("IIF(`x` <= `y`, `x`, `y`)")) expect_equal(translate_sql(pmax(x, y)), sql("IIF(`x` <= `y`, `y`, `x`)")) expect_equal(translate_sql(Sys.Date()), sql("DATE()")) # paste() expect_equal(translate_sql(paste(x, y, sep = "+")), sql("`x` & '+' & `y`")) expect_equal(translate_sql(paste0(x, y)), sql("`x` & `y`")) expect_error(translate_sql(paste(x, collapse = "-")),"`collapse` not supported") }) test_that("custom aggregators translated correctly", { local_con(simulate_access()) expect_equal(translate_sql(sd(x, na.rm = TRUE), window = FALSE), sql("STDEV(`x`)")) expect_equal(translate_sql(var(x, na.rm = TRUE), window = FALSE), sql("VAR(`x`)")) expect_error(translate_sql(cor(x), window = FALSE), "not available") expect_error(translate_sql(cov(x), window = FALSE), "not available") expect_error(translate_sql(n_distinct(x), window = FALSE), "not available") }) test_that("custom escaping works as expected", { con <- simulate_access() expect_equal(escape(TRUE, con = con), sql("-1")) expect_equal(escape(FALSE, con = con), sql("0")) expect_equal(escape(NA, con = con), sql("NULL")) expect_equal(escape(as.Date("2020-01-01"), con = con), sql("#2020-01-01#")) expect_equal(escape(as.Date(NA), con = con), sql("NULL")) expect_equal(escape(as.POSIXct("2020-01-01"), con = con), sql("#2020-01-01 00:00:00#")) expect_equal(escape(as.POSIXct(NA), con = con), sql("NULL")) }) test_that("queries translate correctly", { mf <- lazy_frame(x = 1, con = simulate_access()) expect_snapshot(mf %>% head()) }) dbplyr/tests/testthat/test-backend-hana.R0000644000176200001440000000051414002647450020146 0ustar liggesuserstest_that("custom string translations", { local_con(simulate_hana()) expect_snapshot(translate_sql(paste0("a", "b"))) expect_snapshot(translate_sql(paste("a", "b"))) expect_snapshot(translate_sql(substr(x, 2, 4))) expect_snapshot(translate_sql(substring(x, 2, 4))) expect_snapshot(translate_sql(str_sub(x, 2, -2))) }) dbplyr/tests/testthat/test-partial-eval.R0000644000176200001440000001001114015731174020225 0ustar liggesuserstest_that("namespace operators always evaluated locally", { expect_equal(partial_eval(quote(base::sum(1, 2))), 3) expect_equal(partial_eval(quote(base:::sum(1, 2))), 3) }) test_that("namespaced calls to dplyr functions are stripped", { expect_equal(partial_eval(quote(dplyr::n())), expr(n())) }) test_that("use quosure environment for unevaluted formulas", { x <- 1 expect_equal(partial_eval(expr(~x)), quote(~1)) }) test_that("can look up inlined function", { expect_equal( partial_eval(expr((!!mean)(x)), vars = "x"), expr(mean(x)) ) }) test_that("respects tidy evaluation pronouns", { x <- "X" X <- "XX" expect_equal(partial_eval(expr(.data$x)), expr(x)) expect_equal(partial_eval(expr(.data[["x"]])), expr(x)) expect_equal(partial_eval(expr(.data[[x]])), expr(X)) expect_equal(partial_eval(expr(.env$x)), "X") expect_equal(partial_eval(expr(.env[["x"]])), "X") expect_equal(partial_eval(expr(.env[[x]])), "XX") }) test_that("fails with multi-classes", { x <- structure(list(), class = c('a', 'b')) expect_error(partial_eval(x), "Unknown input type", fixed = TRUE) }) # across() ---------------------------------------------------------------- # test partial_eval_across() indirectly via SQL generation test_that("across() translates character vectors", { lf <- lazy_frame(a = 1, b = 2) expect_snapshot(lf %>% summarise(across(a:b, "log"))) expect_snapshot(lf %>% summarise(across(a:b, "log", base = 2))) expect_snapshot(lf %>% summarise(across(a, c("log", "exp")))) out <- lf %>% summarise(across(a:b, c(x = "log", y = "exp"))) expect_equal(colnames(out), c("a_x", "a_y", "b_x", "b_y")) }) test_that("across() translates functions", { lf <- lazy_frame(a = 1, b = 2) expect_snapshot(lf %>% summarise(across(a:b, log))) expect_snapshot(lf %>% summarise(across(a:b, log, base = 2))) expect_snapshot(lf %>% summarise(across(a:b, list(log, exp)))) out <- lf %>% summarise(across(a:b, list(x = log, y = exp))) expect_equal(colnames(out), c("a_x", "a_y", "b_x", "b_y")) }) test_that("untranslatable functions are preserved", { lf <- lazy_frame(a = 1, b = 2) expect_snapshot(lf %>% summarise(across(a:b, SQL_LOG))) }) test_that("across() translates formulas", { lf <- lazy_frame(a = 1, b = 2) expect_snapshot(lf %>% summarise(across(a:b, ~ log(.x, 2)))) expect_snapshot(lf %>% summarise(across(a:b, list(~ log(.x, 2))))) }) test_that("across() translates NULL", { lf <- lazy_frame(a = 1, b = 2) expect_snapshot(lf %>% mutate(across(a:b))) }) test_that("can control names", { lf <- lazy_frame(a = 1, b = 2) out <- lf %>% summarise(across(a:b, c("log", "exp"), .names = "{.fn}_{.col}")) expect_equal(colnames(out), c("log_a", "exp_a", "log_b", "exp_b")) }) test_that("old _at functions continue to work", { withr::local_options(lifecycle_verbosity = "quiet") lf <- lazy_frame(a = 1, b = 2) expect_snapshot(lf %>% dplyr::summarise_at(dplyr::vars(a:b), "sum")) expect_snapshot(lf %>% dplyr::summarise_at(dplyr::vars(a:b), sum)) expect_snapshot(lf %>% dplyr::summarise_at(dplyr::vars(a:b), ~ sum(.))) }) # if_all ------------------------------------------------------------------ test_that("if_all translations names, strings, and formulas", { lf <- lazy_frame(a = 1, b = 2) expect_equal(capture_if_all(lf, if_all(a, is.na)), expr(is.na(a))) expect_equal(capture_if_all(lf, if_all(a, "is.na")), expr(is.na(a))) expect_equal(capture_if_all(lf, if_all(a, ~ is.na(.))), expr(is.na(a))) }) test_that("if_all collapses multiple expresions", { lf <- lazy_frame(data.frame(a = 1, b = 2)) expect_equal( capture_if_all(lf, if_all(everything(), is.na)), expr(is.na(a) & is.na(b)) ) }) test_that("if_all/any works in filter()", { lf <- lazy_frame(a = 1, b = 2) expect_snapshot(lf %>% filter(if_all(a:b, ~ . > 0))) expect_snapshot(lf %>% filter(if_any(a:b, ~ . > 0))) }) test_that("if_all/any works in mutate()", { lf <- lazy_frame(a = 1, b = 2) expect_snapshot(lf %>% mutate(c = if_all(a:b, ~ . > 0))) expect_snapshot(lf %>% mutate(c = if_any(a:b, ~ . > 0))) }) dbplyr/tests/testthat/test-translate-sql-window.R0000644000176200001440000000562414002647450021760 0ustar liggesuserstest_that("aggregation functions warn once if na.rm = FALSE", { local_con(simulate_dbi()) sql_mean <- win_aggregate("MEAN") expect_warning(sql_mean("x"), "Missing values") expect_warning(sql_mean("x"), NA) expect_warning(sql_mean("x", na.rm = TRUE), NA) }) test_that("window functions without group have empty over", { expect_equal(translate_sql(n()), sql("COUNT(*) OVER ()")) expect_equal(translate_sql(sum(x, na.rm = TRUE)), sql("SUM(`x`) OVER ()")) }) test_that("aggregating window functions ignore order_by", { expect_equal( translate_sql(n(), vars_order = "x"), sql("COUNT(*) OVER ()") ) expect_equal( translate_sql(sum(x, na.rm = TRUE), vars_order = "x"), sql("SUM(`x`) OVER ()") ) }) test_that("order_by overrides default ordering", { expect_equal( translate_sql(order_by(y, cumsum(x)), vars_order = "x"), sql("SUM(`x`) OVER (ORDER BY `y` ROWS UNBOUNDED PRECEDING)") ) expect_equal( translate_sql(order_by(y, cummean(x)), vars_order = "x"), sql("AVG(`x`) OVER (ORDER BY `y` ROWS UNBOUNDED PRECEDING)") ) expect_equal( translate_sql(order_by(y, cummin(x)), vars_order = "x"), sql("MIN(`x`) OVER (ORDER BY `y` ROWS UNBOUNDED PRECEDING)") ) expect_equal( translate_sql(order_by(y, cummax(x)), vars_order = "x"), sql("MAX(`x`) OVER (ORDER BY `y` ROWS UNBOUNDED PRECEDING)") ) }) test_that("cumulative windows warn if no order", { expect_warning(translate_sql(cumsum(x)), "does not have explicit order") expect_warning(translate_sql(cumsum(x), vars_order = "x"), NA) }) test_that("ntile always casts to integer", { expect_equal( translate_sql(ntile(x, 10.5)), sql("NTILE(10) OVER (ORDER BY `x`)") ) }) test_that("first, last, and nth translated to _value", { expect_equal( translate_sql(first(x)), sql("FIRST_VALUE(`x`) OVER ()") ) expect_equal( translate_sql(last(x), vars_order = "a", vars_frame = c(0, Inf)), sql("LAST_VALUE(`x`) OVER (ORDER BY `a` ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)") ) expect_equal( translate_sql(nth(x, 3), vars_order = "a", vars_frame = c(-Inf, 0)), sql("NTH_VALUE(`x`, 3) OVER (ORDER BY `a` ROWS UNBOUNDED PRECEDING)") ) }) test_that("can override frame of recycled functions", { expect_equal( translate_sql(sum(x, na.rm = TRUE), vars_frame = c(-1, 0), vars_order = "y"), sql("SUM(`x`) OVER (ORDER BY `y` ROWS 1 PRECEDING)") ) }) # win_over ---------------------------------------------------------------- test_that("over() only requires first argument", { local_con(simulate_dbi()) expect_equal(win_over("X"), sql("'X' OVER ()")) }) test_that("multiple group by or order values don't have parens", { local_con(simulate_dbi()) expect_equal( win_over(ident("x"), order = c("x", "y")), sql("`x` OVER (ORDER BY `x`, `y`)") ) expect_equal( win_over(ident("x"), partition = c("x", "y")), sql("`x` OVER (PARTITION BY `x`, `y`)") ) }) dbplyr/tests/testthat/test-backend-odbc.R0000644000176200001440000000143714002647450020153 0ustar liggesuserstest_that("custom scalar translated correctly", { local_con(simulate_odbc()) expect_equal(translate_sql(as.numeric(x)), sql("CAST(`x` AS DOUBLE)")) expect_equal(translate_sql(as.double(x)), sql("CAST(`x` AS DOUBLE)")) expect_equal(translate_sql(as.integer(x)), sql("CAST(`x` AS INT)")) expect_equal(translate_sql(as.character(x)), sql("CAST(`x` AS STRING)")) }) test_that("custom aggregators translated correctly", { local_con(simulate_odbc()) expect_equal( translate_sql(sd(x, na.rm = TRUE), window = FALSE), sql("STDDEV_SAMP(`x`)") ) }) test_that("custom window functions translated correctly", { local_con(simulate_odbc()) expect_equal( translate_sql(sd(x, na.rm = TRUE)), sql("STDDEV_SAMP(`x`) OVER ()") ) }) dbplyr/tests/testthat/test-verb-filter.R0000644000176200001440000000345014002647450020075 0ustar liggesuserstest_that("filter captures local variables", { mf <- memdb_frame(x = 1:5, y = 5:1) z <- 3 df1 <- mf %>% filter(x > z) %>% collect() df2 <- mf %>% collect() %>% filter(x > z) expect_equal_tbl(df1, df2) }) test_that("two filters equivalent to one", { mf <- memdb_frame(x = 1:5, y = 5:1) df1 <- mf %>% filter(x > 3) %>% filter(y < 3) df2 <- mf %>% filter(x > 3, y < 3) expect_equal_tbl(df1, df2) }) test_that("each argument gets implicit parens", { mf <- memdb_frame( v1 = c("a", "b", "a", "b"), v2 = c("b", "a", "a", "b"), v3 = c("a", "b", "c", "d") ) mf1 <- mf %>% filter((v1 == "a" | v2 == "a") & v3 == "a") mf2 <- mf %>% filter(v1 == "a" | v2 == "a", v3 == "a") expect_equal_tbl(mf1, mf2) }) # SQL generation -------------------------------------------------------- test_that("filter calls windowed versions of sql functions", { df1 <- memdb_frame(x = 1:10, g = rep(c(1, 2), each = 5)) out <- df1 %>% group_by(g) %>% filter(dplyr::row_number(x) < 3) %>% collect() expect_equal(out$x, c(1L, 2L, 6L, 7L)) }) test_that("recycled aggregates generate window function", { df1 <- memdb_frame(x = 1:10, g = rep(c(1, 2), each = 5)) out <- df1 %>% group_by(g) %>% filter(x > mean(x, na.rm = TRUE)) %>% collect() expect_equal(out$x, c(4L, 5L, 9L, 10L)) }) test_that("cumulative aggregates generate window function", { df1 <- memdb_frame(x = c(1:3, 2:4), g = rep(c(1, 2), each = 3)) out <- df1 %>% group_by(g) %>% window_order(x) %>% filter(cumsum(x) > 3) expect_equal(pull(out, x), c(3L, 3L, 4L)) }) # sql_build --------------------------------------------------------------- test_that("filter generates simple expressions", { out <- lazy_frame(x = 1) %>% filter(x > 1L) %>% sql_build() expect_equal(out$where, sql('`x` > 1')) }) dbplyr/tests/testthat.R0000644000176200001440000000007013415745770014705 0ustar liggesuserslibrary(testthat) library(dbplyr) test_check("dbplyr") dbplyr/vignettes/0000755000176200001440000000000014033043057013555 5ustar liggesusersdbplyr/vignettes/windows.png0000644000176200001440000015427313415745770016007 0ustar liggesusersPNG  IHDR9psRGB pHYs.#.#x?viTXtXML:com.adobe.xmp 5 2 1 2@IDATxsSXq(Nq(.EJ")x o)wwwwzlrvd+3Lgf0}? H@$  H@ vZ H@$  H@@u@$  H@t0[$  H@$  K$  H@$0ԥIߺ%  H@$  H`x{w]w=7SOb8-~>wG4Ls1 3*W'dKlS!{.]>nSN9r-_{wg}n%\\6}L,V$ K`(ֶ.bw*^`eYRdy[ou֪;,;l|[oc]h~N8!.2_u`dTSME+!`sfss7rK H@$<֜{ꫯp묳/TW^{ 7_0}'7,nLJ x>!: K&l2z'kү9眓iξ vۍ]/N z[o5#M3+N{UW \\6 EGb%  H@ Ђs^>qc}rfmƍK.nYz2S#rE%%d樣{wp/}Q 6?C1|=C`p|Ms\_bpcK/F(_pl; q.x.F;#j$ ȫv-Dk>Fi4Zbڲn=`"~N8U%=k_Z+g0L4D3^q_c5`ZI'AadU45 3cVȽ GrqXs7Z>8i9R $  @KofڌEqj.'xD)$MX,Zf.˘cXBn]x0_}Zmժꨘ\Ws7tAw%  H@C@{W\#g{n+7 쩋_dp0W_}1]曳d뮻YތW,׽ꪫ7glbYX;9(lynDvҎE'J4KѧN%w$K H@$Pt)=2 9XcQF/tK\1~r{k4F.w}zjvl eA1;C: &d裏>0Fj)ns.c^H*4"'XVЁݤIvFA[lH@@A0r%rN%/{Jg[WZO1x n84Q{I9t3ơ+2Dgk&kwdAQe:_oau/h3%tR=u]Mb-xꨘQ9g[ިodkj#gi$ :^V"5_zmZj?O5qG;s͑Hq>^q)s =}~wfYhA<^m8~bJb9{N6ߑ61X H@@ _:"3Yf=wQG$I#]v!<הomAJ?C5hO1mWȒF!9`lV d)y睗ut4`Vկ3v< ~xPnl^n:rYձ7ћmR#mbh$ R(K7twqix<# 겘g1EpVZil8C[n!f9wBf5ۃYlRE^pxO*袋8K,D4o%wH}>+ H@n/n:[`.FڼsezkNQ8I6fc_-b|`!7}m_? 5cM-x.F $  HW^;r6r.+ Q /0z릛nQyG.e0L?ؚѐ;[y~xZ|17x#3<*x} 32xL\Y eea%$  t ֬4lm}AEiW|G*ގ< (mGC%\ "=Ӄ9묳:_J:KQ$  wgHo,2X'm =حʐ଩暫EZF Q.$  4G@]?sK@$  H@@s<9~斀$  H@$ Kgn H@$  H@h9~斀$  H@$ Kgn H@$  H@h9~斀$  H@$ Kgn H@$  H@h9~斀$  H@$ Kgn H@$  H@h9~斀$  H@$ Kgn H@$  H@h9~斀$  H@$ Kgn H@$  H@h9~斀$  H@$ Kgn H@$  H@h9~斀$  H@$ Kgn H@$  H@h9~斀$  H@$ Kgn H@$  H@h9~斀$  H@$ Kgn H@$  H@h9~斀$  H@$ Kgn H@$  H@h9~斀$  H@$ Kgn H@$  H@h9~斀$  H@$ Kgn H@$  H@h9~斀$  H@$ Kgn ~ۯ|f$  H@@ d$P/_|1#O9 7\i$  H@~߿ u6zT+%Unl覒ݒ1V]4# H@V~`eQVUFܧ._o&ĕSdgJ76Jgp{InӠFZ$  H _ځ$ H@$  H@p ͓$  H@$ԥ]6O$  H@@Pvxi$  H@$ .'.y$  H@:;H$  H@$  t9uiw͓$  H@$ԥA' H@$  H Km$  H@$ '.< H@$  H@]N@]l$  H@$  t8uiwI@$  H@r.`' H@$  H K;4O$  H@@Pvy< H@$ >{~ߕJ@]V. H@H.va:N4N$P| VX0X`FU4S{ ?Ґ䫯 5X3Ovi'u] 5oN_3^#.-]i$  |)\s8,ə!ڒQZTQoyC38;~wqdž&E.o~Z0o_}g5E@]Zroz{z7@#h~Qr_ܤo;Jl ?Xgf},0s9;6M@k ,MIԳ:K.a3%dI~/2)ƀ9s衇7|/b-6#vl^yܞ6Zo">Gu5{7fmxww\V?)*W_=3N8aS׌,L{rǮYW}fIn`!}MlLP|}#K/MH*a[ ZPuQNvORDԪ:c6ld}ٯHS[oumF/4L[_3M@S{#0IОa?+kkowyE4L>FmcQu_,W/L`O+N#Y֥N<Ĺ YMM>*^5?R?O6SN9%MoU`O>BD Dr1"vi#\,E$黙go$#7@k&dd0l͈J9Cu]1O$YwuoO.j6`Ұ10>MdT_HϑN(!N\s12\<> EHblQ ?1Yg\ HCTZhG[!ZhUF܎ΐ[fmQ{,(S%5aVSGq[]+zVKQ uYve#&nQn1?Flq Jկ" qۊx -o*9OmR1C %r0'BjF6lBjS;FxDiq_~aes1B @RJ>uY*$4& R Ȁg1b/BdRbǦIU(GEFRˢ.G i" R*1ǁ65YKXIc Qu&ѥ bVMx>,%\K^d|kysDԥ$:*3nΚiL !^ x:묃'=5j5並Xy83i 0d 7,OptMD7#MBA"M $A|?"b@p  +Bi>(6ʢbVHҴcrerl&Fʹ4ȂM@WcOhns7δ -b 9@RF@Qge`3'gyɴT8;@0{,Tf3UGɬى .aӟS6a4|c (|RBqJo 1[lñM6avFfe68yiuSW|c^vr 2 O:$˧ /g %Ҁ:kI ތS4(')br)Y ԿLR 'ok%H{{7>w!v<0BGe5,Bc1-wl5f#S'n.)ꩧJmo Y~V]d kې;Z( W:~Ield-1˂_:e6yV В.~Lpj:є,er""ID8`. ;fuu_52 rdSE%,!KsJ b|LXj &\$;!ӟP3ihg9153+Ĺt xB -R $ЙԙiJM‚3ϯQ&k"{S+Z6br\ԿLEކs R ~7elGa?ir1xO8b3'[bc.M/2QwIV)e bɥYHb8eqshApBSNA4(/PE|`Mo͢"i5K[滽E%~}#EP1Y┗j @[>uY32nİE~ba'S!QG467 cOaXӤjcXR.(XYN_IagKNW3&9:+VP+ :$ryr @'PvB/hCNXvm#0`AWv9WVq [6'b^/cxX?-z0wr˝wމ.E36Q%8%WT6$ ' Ra<, -2&X ϖ_?=o0w^5z96xcc=ڒs~8#̤L84 US&khYSVVmLK @ ȕ]]d,dNAS0gnt):_uH⦰*wZrNq&^K[W$WTYRQK≉O8>ٳ\ / f+0WB1,brټ%^"0WB6=o.kdKh:A6{uwŗ P2s(4Mj_S"}Fýy%_,̺XzW\p-ٺrp.wy0_]rOɖpg` ҪQ/=i.׿is}mPݨ14iaR f/eZ8]Q< I)\%vY]֯e 8) glxmȰژxngV5^~,"c$ͣMΕbd9#KQO|{)̘Tr@kSz{z@'PvB/hCftFl /\3Mgk n3҃kXSgw`EL5 z'dͤᾐ-^<" AqǣVb.b"nHNNeCxĄ"I-˳LM"4<(K8ZѥD!J+.q,wBŨh҆{F_IVYs,M*RV5sNœPhPbǢ7e,e)K⋑CM%]㴯~Nü$vrj88겗-#e h[E'dFqG[G+3c!-["c$+\)`v||CID_܎ҪTlߢ|;c7A?skد^I` KUw?~Gy Go*?E뭷^4+NӁ QN)p/8iDDR7㗿%~lv3Ys(&v{55N:Y] (c\EC;C(RktZĆT`O XVs8~;ZW4cرw"%M9[K0;;z~[R˲Yq0kUFX9e8x=uYQ:Jjvo#;#*?-Ј爢3yPMZ;MpzhFC\PFɁ}$  H@$ ԥ+ H@$  H@C@]:0E$  H@jPb$  H@$  up H@$  H@M@]Z$  H@$00ԥZ$  H@$  H6uim.J@$  H@P gk$  H@$ ԥ+ H@$  H@C@]:0E$  H@jPb$  H@$  up H@$  H@M@]Z$  H@$00ԥZ$  H@$  H6uim.J@$  H@P gk$  H@$ ԥ+ H@$  H@C@]:0E$  H@jPb$  H@$  up H@$  H@M@]Z$  H@$00ԥZ$  H@$  H6uim.J@$  H@P gk$  H@$ ԥ+ H@$  H@C@]:0E$  H@jPb$  H@$  up H@$  H@M@]Z$  H@$00ԥZ$  H@$  H6uim.J@$  H@P gk$  H@$ ԥ+ H@$  H@C@]:0E$  H@jPb$  H@$  up H@$  H@M@]Z$  H@$00ԥZ$  H@^|?pM H@ $04\ve;A44k+G?g EL?8")=G}ꩧ>Sk5\3N\u]E5O?M뮻n_3^G[! H@}#-C\s8֛Ƶ-2Jw񨣎j{_lh8W?O?4ok>M/ tợB$0YTr_{w7ߜjzKx +x9 L7t=ܨ9&i$0ԥIۺ4/o۸a&hE]t'd(a}ꫯb37 -<6kAW^9h#[&J{?~r ;;,STz뫯zg&pU55Y lǮYW~/`!}MwLP|;#x ;ET/@M*}YH4-˯4L+9]Ź H@FV=(oy6:nv~馛^vtfs]w?׿~_w͝IR:q7餓N1c9&+xț8`iEr7RK]y)k&pm&;y]iRz'͙"WYev>K/MH*a[ ~}ه('6~'"jUWc1fm6>W]uU$N[ou6sjDKDѡb-I|& )j~G$h0 ~ԕ|F|{ XL3Jay{1k꥗ fF~495l]*O{tPzfүja0 S3YCZk-./gO}uYV\pA0B?`ְY{ C 12eKq+Ը{Dd?(mi>$X}2z}рL'yϻ4&e\{ӿ/\2my*Y32nkW^9 /21 M7ݔY087z@_% 'Pi}mhSTihs;P&J#~ 7d R;j_e92fPRs[uVK Yve#/ (F8F SHܵ%1H.%w\^}q2EL6"^|ٕʞ@'Jկ" qx _J%go9Vn)c!S\98%ٰ FV#b4d$11Tc6P];1R8f)E1}TH 0IAiL@'38cih@2r1B_PR=܃QQxc@F /XSO=EL]r5ui/fd~i4*rp/__IN8ᄴj{{whS. ,hZ19(UV)laxrir3Ѭ(2[;BZ' )#}TH 7)Nհipov2`G7Y(n6shlv JX: ^j3@, ~헳K H,/)2`E,gIU<6!l TXp- !BMpƻ]>rA&ꩧJ-_=21C&ʍ5|S 8 s9gj?a:]$CIJP6Ee_2<+6fJW_?&[{X5BKYRҿ}l9n'lӥչӃ.2,-E?Ζjj.|lduxRi?s5WֶX1, ~bvJD 6sCj+h} j:XjV85)%M0z͇E>kf. qu$1m]wdI$G"VQȋ$ʚEQE,,hCCVk&^w{^VSeFF4ٺ"ACK9 ]ڧ.|]}{[ǂX3Ll| reƤ|rKuůe Pg5, t2ui'goC@,^l[SrzVah<^c-v6f]ȸd]-rwK=c?XHQ 7k_~b!mGnu91a|CՑj62X oXiJ&[/rNۄ2 wY*96xcc=vB[qNo`$ۃ9a+"ۆ{@R~\ܻrRWtѭIN&h&6, tuiGut.*i)ޞ6Ve2 t2{|_pobeY X .e1ɺR_Ne.,CxH38fxf?lȖVzϬL9}bDq5oSU3p{zaR]GD):_r%$hEp.c;9Y]˼s)[{FEg}wqŻujl7:U[Y|$._0 $ $.~Ѫ.!0,l wo<`&c`A].mk_Xx G?QT$ßɣV8y4ʁdDSa}W皒&9\Bƙ'&CF$ZCbC,DF$G"q0XGcG+Q5"6Di_ѥl A!/QqM3NP8x)eP9UQ'aNB4rrrq>MwYXMKi_LJ;98at\u-DXr~Z32|(yTvYi6gy&X<|TLx H K٪?kUie/4-͹+` %5;Umeo:묓e @zhهZy(ͶI<}Q gHgj# gl6MC.Tt62 PVJ(848㎑&+f$7!·=p2L>sIt\F3p&,XII+ uԩŶd򢟳rUx& YRLo痒?mTBuqk+[^d`0$48mL@Xj H,2m5Q%6iq^Y>ۜx@"52ڜeU%5;KkռUM( \]x8LQ<%I/%U\"6]CK+]S^)B'дٿ@Koδ1Nbn: f3y~mLjiH@iAV>eO1F 35|DZj^-h|0;dw.d@P#쎪 Vo']  emN81f' H<-M]FguzE9~U>ɶns$Э<[{vu~-dپ :L'sv&kLgy@k5IO;g1t]ve/ة;PX t2ksFC\Vv%) Ӄ 2I:1XQ:Kjv4b%58d)5KYv_9khC$0t/:}mK%  H@@{9:9uyqe]K?{p $  H`cP`%=|~i-$  H@$PF26K@$  H@K[" H@$  Hԥe5m$  H@$=ԥӗD$  H@@ Kk, H@$  H{K/m$  H@$ 2P״Y$  H@@PvO_ H@$  H@e$.-ci$  H@$ !.ힾ%$  H@H@]Z^f H@$  H@C@]=}iK$  H@$  $  H@{ҖH@$  H@(#ui{M%  H@$  tui-$  H@$PF2{6Q@IDAT6K@$  H@K[" H@$  Hԥe5m$  H@$=ԥӗD$  H@@ Kk, H@$  H{K/m$  H@$ 2P״Y$  H@@PvO_ H@$  H@e$.-ci$  H@$ !.ힾ%$  H@H@]Z^f H@$  H@C@]=}iK$  H@$  $  H@{ҖH@$  H@(#ui{M%  H@$  tui-$  H@$PF26K@$  H@K[" H@$  Hԥe5m$  H@$=ԥӗD$  H@@ Kk, H@$  H{K/m$  H@>SK@hui;Z$  t4.la=蠃:1woxm*VX!|k,hiM{'S;#hW_]x2<4L vi'u]\\s͂y]^P:$  H`(o5\387i*>O“.NgosF[Rs7GuT4gqwN?o8~Z0o_^K@Rǀ$  H>~l/|}T+>ժs97HռkmٛoY{wdgy"8judwq&|7x}2Fa[lŊDXsexWp{6h59ꨣ8∽%h2{7) wqYY(n .O>IV-LzKSE PLz{CݰFyj&pSZoe5 &~G|Wx{%_4H?S<_|O52R8o6 7\&G-u>70a*I@O[c(Boh;뮻'x׿f 4EJ0Mr.B4e'pqM:SL1Řc 䖳"X:ӢZ}_j5XcM6odMv'J+=1O>93E*讴G]z饩 CE;Cz+Է4o=|RĮO>$UZ 2,S)30i1L<'p9 L%X4:Cf-"j~/fi1Fe/R‰'8s>oDg뮻b#<&}9`bf`[o agjruNpO< W\H3̽KGKIC??[eۤ+i",rie߭f毶j1w(FB>(,QL2$ڠ# n@~צBp_r!vYAnhu]wJ.?=쳩R@`VUת^Nif~f폟w5K1{fW7%1mq饗Ì6mƝ񪫮J ) ٧HkA#l$ ]zk4DM4#"G?]tQİK,D\^q2 [k%$=K?җCC(g?ȋ#ЄЮ/~ |' b qِw-(@:$iB($nmEQz)a' 6PT[. Jw}w>(Zkm馨fZB__z%;xh;۔ _:2@ifxNb%* BTLm k6'U"? eY8',>&1e'ٕj&$:ȋϟlfF [a H iXȔ}+f ;Lr^$ 炠L8' $yۍ;`&Ff \1x縉la[)8zꩄY9$P xE7[1sS܆Ip|%a,Q%Ux,믿~*=ְ Ş]2rvT/dmf8fմ __rR$kGO W]uM)>{*O8ɾUǀI<>IgKf9M}C\Hvfd?q|.23+7;~ƤˌB -SORLA -뾲?NyW%ja7u,+;X({w5X6[G!ˈPAOSRyp1~뮻;qV*bDp"橪5Kn&zrѽh0U)>96oįJ DJfs; ̵ׁͤzdK#RBs&RV:l EqR_Nz.̧HХ|\Bb r,(`5W[p_Ӱzx H` K5ɱ=;gZQK 4@ֆƽ2>+l±k4yr-wy'x%yHŽqz8&WZ$ Y7f^V?5V@D7!`"{pWR&b$k".=-e h$oƜeE8OR*Leשdι^!mO;E(= AARn6 ѥUGV(alP v++s)dKƬ3ޞ\doxz{x t> ތ[pCt1dkq)ee[u/>R|ηxH38fxzd?lȖ0l?gGS}+EF5*~gaYlÎ\ Mz8_H.LRK\Er^uZ6қlN*N^aw 'U_G.FE hy>3o˹uUL侺c$ A'𿯼A7E$P%ON ˎ +-xo)J<)P(@$er ` p1v؁BI3uK$Йԥ/ZU0^;2U Yčl4 G"h0t#J5(W^y%>vQ..YDJ8EoPKĝ4r2CFJ4*>ė"6$c "45E~vm]ѓ%ܣ )^93DiRHʤ("z#Pt u..VD?G-wྣsy?LqS$w c=H%<u'4, k<Ҟ"-Ӭ "&ѥUU]Ǚ)#ND:$0aUUjU.4Ja3?Ã=0xgeӝ{EpnY9ʄ\>`o{mTgʟ)y+sTYʇ^O$P&#%  H -ǛG^#+l(#FLY/=#P|֫[ٻ?Rҿfe*O?Z%P#VF=' H@@xD$ !AGiU{zk,s >X=bu{#WfוG,؏s=*OQi{? I@$?0$ H@C2Vwi_y<鎿LRF/~؛JJp_h;爛xt$  B@] %  H |IK2pni1kei*KX&K5lxx')|nzsB1Uf2gx<4 H@hY旀$ x䞞͟NPʟ,>YezϞ"lC~]_4I]YoRYjʮWrTYpE$ uS{뭞uPN3ǵk*e0:x?*gq&x*J76JgpQIn~,ahpjыT産~!\?2蕯ُJǓW.X$  H@腀^- H@CrtE:ߘ=x9+{ost%+ H@}$.#0K@vjWܬĕE&2Zs$- H@ 1  H@@ ;lgT<3Ssl$  4".mD%  H@}%"gk&K@,ٮ$  H@:#A#$  H@$  Y!6\$  H@@GPvD7h$  H@$ !K@]:dކK@$  H@Ҏ$  H@$0d Klp H@$  H@A@]ݠ$  H@,uz. H@$  H#K;4B$  H@%.]o%  H@$  tuiGtFH@$  H@ԥCm$  H@$ .n H@$  H@Ctv $  H@$ԥ ! H@$  H`Pٮ$  H@:#A#$  H@$  Y!6\$  H@@GPvD7h$  H@$ !K@]:dކK@$  H@Ҏ$  H@$0d Klp H@$  H@A@]ݠ$  H@,uz. H`x9昻{";<]>î ў7|ƺKڼno~͖$А!"H@jx饗N8oe=8ӥ }G}ߖOz^޸袋|We3\{% vPJ@@︱oRD_>|G?ss9g_3MMJM,VCC|7x}ꫯ0M7b-6䓑Ffo/~MswW(R6~uGfaEu .O>IV>`GuQ{Kblrl_Ǥ4 K#ϖ ':2Űyqeoz+3:L 0ث1$O{c9f_3fmtAHud dpBfwW`$ԥGZ'p737~_6^{5$ TOtM_|q9D#C AH^z &Hq7̻?8K@g"iyE5EG}gAu](s%` ^n! Ut-1TS1cUmn믿>c!K>S,첽;rM?X_>m%4:ؚ tI3<3 }z]OC'?&1uKo 0PI9묳2b \{9_c5gTcrry~3&#49&iUW] #j%Ri\N^{~QJ t/bSSC3S! 3j4  .SkK~eEiJ̭vm / Ah+ 6zs=ߎJ Hё??B.[H@¡+Ȏ~ w_u$xW18c1By}}?ڀo>1 D6 B+{r)YCj^s5QH+}J+U'H@mع{, fFr{ai!/Jz뭇[uU)*ղnf\"Q(ÿ[mU$(Rn6G3/nA= `vHuYCl)en&(Lʇb{a"pR Dgf *@fY;YEgxQ4A$҃c=6 ]w]J#'9C_.Zz=.D"20XIaX"_sAi$g"fI'Zas=УR'oa$>l_xI٧`΀$0zh@p;J =,ovpL t qQelrsX}Չa M5.[na~(`\?½2K!X>26D-+~- (=96 El\C02SWCGh*H/4;S$[rv pDuYwq@ E:\xh8##<\|% <@R2bK,ѰEO=AbR`j@* aށb\X܌))H4 BmH$T|!R J2:wtdaWPJKSqAiވUØa /+E7"ۘdB{lnVJf@ǧ/*$JqL:Nm(Jrӽ9uco JLmPV*'-l+< ǫ%#I;<6[ֆ (0C 5`$Jy #Zeq\,FHs?ZLTd~D)(F4!;kSd{R锆İh&2m*"rxh|5\)GH QNn.R*[`p9 a3BB5MW9S:Nd dϹclڞabi+lS,LےӮt )M(7ȕD6ĉ,| Nrfr4OT HK;n TCbRG`&y"eb3ރomM!-8!G䰜\.áZC!{xlХ|%b“TG 8ˉ*6O2ds$4$A\ o5\3K+k\yz)Y)UC2[`6̲e6޹&˅9e8⍷PEMpq3yDI.&{%e6Muyr62@lã C؇SO\󽔀tp[{n \pA_UtZ"2V qˎnIfecR5K9!J9,292.XgH'leL. Y7f^֣Rr.V85P_ʡS59)0nm`v+Rcu٘:ТޘHY O)&qDSQGT+\t_T&I8(Vp : kLF5k[_uJ< ~tл@Zk!RlaM-d@^>y8B)(tҬdž,cu"e{Pž8KDA|P դ8W1U[!kOkÜy-IH4`6>ѫ|sn.Q,e)5 5qtͪJ6v?{Wgs%2яGޜ.mD)48'jYˍﴎ4g[ dlS!wɅ6cdf#Zj)NDv̦˂fTfV(NK֋S $ N .^ІB8nPJ;!6P0q[YPbx.jǤbA)DZQ"Syb7$--c'7I" Ibr^Q †T.Ō6di_[ot*d+x4΢㩒<+ ז[n?<98׿u(1=PpqS, _*,f8lJ!\PP}Sc0}ʌ `UCձi9 9azx[O-ŋy~-agӰvt)|b'} sth H` Ku5E!~u}蚪uhsZoItL9/q7q|1Itq*ofy$ڕ_|Gh[4$`AKANpj( ҭsҥIi.">qpZC)b@RFqjLq+T>ژ#8խ<0Gݒj ħlSN9%{WlX9X|'tUڼb6h#OQ`!V!= #"C{BCo2CK<2 CP`S9qF!Y5MJF :asLƩ LLb;F겗HY0vDKQf #g0[a H` K 4xl:Wմ4imn) @֖[yh8MkX(ih*TAĠZy,5D U@8-5VKͥ(4.ė"6d lwEǖ0֢-3#6pCryHYTm+zH JӃ^[TUzUWQPS} P&E@lʒc:GnYGeE21O5*qC E7d"f`+TJdg'6?D'23¸#b0s44zunoujL2ь=^Pb7fcK` fs@d,#*唑F)l km`E[A/9PYKgp_R[0xm\`,ׇ(e $7\r ;B"W0O,ja-M&@V|cC9 H&ER]|XsJ%4|ua%dEJ@=#;3S! K1N2soEnbb ؅]݅ݭ(?[QNVD P{>qۻۇϽx=Ngvo>󝙥YFKo {)6=,{ݒtHTK-RZLLmccaHrկ'&}.-mNܒZn[.#"&d/mʴzloQXB={dV+UAbu$EI 0|Ja'~v3igo,%OJŭ?r5֨(ewۣG78;X_駟:~gWmSnLjt>yp aa^x{)=CwMk0ֻ@ ]>Qhȑ#Q_mwDs=vXΔ^xB^s5,H( vYg!ۦfFfڰ=\j7묳r)={|gs㎷zkb4s2,,p K^" c1U_#,gqF,RUӘTۼ?Ν;wԩTI wRZa0|~2.K., (v9;?f]lb|1b_#bN^Aԝbg̾ڵkR~wr%' 4_ql{/UnСC=4<Ka/ddV/~S҇u ܪk0}E@@VWEh{l-`n|_{5F/t԰bĹm3SO=tM2$mD.ugFj[nz[m_/bt^z)W_}uW_!k3藳cp')/Y2fv#X>ɋtlQ|I!WXau]LI>Td9` V[ Pҍ7H ȳg}&AIlǷ~ۧON'tnfoV s:)D?ClOL bl馛"YM4." .ڡ`PFPDKcy睙ar6E]h_}O!@b v&J-sύ:RTe0OɈ^i}Gh]v\rrɒ@s.mvQD r-Gu6d9F?N^OĹRb .(n5x9KSF g!?ڷ&0^k;<+K,a A\hŽS/,~C܀}{EmK l$:hf ޽{6—-ciVFHSvn喖H/* t2Yf h%4;έO<*SD*szc݀W^Yqbf|6|sU$ޘ(s][xw3S,1 ]wE}EX#M33N+!JlzvRg}'O"nY,FHRƸS.@_^*G{>3h7x,$;%av[aSO+lvi'. aZヒOE0 or"q>DZg|P{ʔ}/8α<{֭ 4́uVb ` fR)8['V/ZO>AӚ&tХ컻_"5\‰5Ԣ8gy,_R Dgf?CXgBE.4mmE0}"9~諒I 'G> +gm65޼%܌%7f:Hd!1 .Շ]c]O <&V]!"tQJ( %9IdpF֨Y1 _on=`goA( dȘO(<ћZ-"KuT6h<Q HVUQQ BءG#̘caܶk4i_YcB"؋@.):ؤ sR2)2!7N|FvӘ3{)R[\laұLl=E4SmP\*gb2M 4#kNKD,KE+ɗ]v_,PRr]O?ٚ,V }hfҥ:*|̘Q9Yła,5ֱT0WJ d0oov1TSYPZ\Ju|OY/tK~me}!#L+DXueSS!kY1[4}.H}Rkt O?43?d>K,&K!s<d$§S;N.Eݩ*/7RYI߲Pds2ɿ97[%+"% ]ڴM@3hsFl@0[ə ȫ,XSYx\6 B~{8P&l 'L9 O6"f5bf13Zv7grᰈ<Ѣv`嗗s$'c%H`Jd.3O,twpڰ7ڑE><N{ffX`l\Κ}fvvɫle^)nCce[o27o%.a:p`ףם|>ҩxk`S-n'$}r2OF4ћJUG" MH@6aH"Жxo;)ST✊<<;F]vم|b^{!W7p:Qp cǴpN1 P=8/%:g`vcz #ʃpN|vIݏ2)?ko?,Ձwr qۿ'xӃ85ǡ-:'\2u}\ ٰtHCen@tM{y)q7 *h,Dgn]Ե=dc^ǎ-K#-4| `. 0 x4މO=|rLQK)/4%F)[{#'*X =SUFوf(fn駟#?L AS_Z^z!0NB01/A%5یɷcaN"A@M@"hWd/Wsq_eWo-4(+\(\vAvE0Tˏ9v9rvS ÙFp@2|ZAe~l@`({5?xaB*sj2hKwv" " " % DEKi jlJj@@/D%!" " " " " "P5Ҫ)@ HtiQD@D@D@D@D@DKkQITM@jt(" " " " " "Pҥ5$D@D@D@D@D@D@& ]Z5:E@T" " " " " " U."Ԁti *  HVNE@D@D@D@D@D@j@@@KF" " " " " " 5 ]ZJBD@D@D@D@D@DjҥUSD.D%!" " " " " "P5Ҫ)@ HtiQD@D@D@D@D@DKkQITM@jt(" " " " " "Pҥ5$D@D@D@D@D@D@& ]Z5:E@T" " " " " " U."Ԁti *  HVNE@D@D@D@D@D@j@@@KF" " " " " " 5 ]ZJBD@D@D@D@D@DjҥUSD.D%!" " " " " "P5Ҫ)@ HtiQD@D@D@D@D@DKkQITM@jt(" " " " " "Pҥ5$D@D@D@D@D@D@& ]Z5:E(*>~Z&(Ak? ʒR_~⭺)*:Ig$Ran&tҗ^zyK\d'w^:tiZKeh*~^SO5UVѣG7,*2x/%t(X,~믿WmOM3?O[E.mR&w}^xW_ꫯUT# ߿;Y{U:?py-|ME47clY7~_ٷXv:7ig["QJX4+M @١g,@Ʈ_5{z+/,mB/8rg֬!C~ƒWx].3F_~姜rJN[uDv}@XkСxEvFruwlXov77GeI&dfXr%C].馛+o&_DnVZiR}٭]}Y:,6lC P)Fp nbs=P>_|PAR L3t)l^c kqE(O>Fm}xn!vxN4D+cy[;{9ox^~B,j}}Ѯ /N;^M;Ђ{Ns=: / kau]G c["A{gh2PO>9&쯸 ~>њ0!uPl榛n{s6_ x D'aK,̒D}Oh;SO=59zUӸtz7.م|>h,RevQ@ptZl+Ҿ{dzÚkI[u-9眳6喧H%X;Ɋ+8rrғ7xc{dQBO*B-^zi4>W(|B -T_EaX2E"n{;7HCNα6H͑ ~9Ǥ^@0e73O>䌭?nEd[gug_~̀SLR#: OnVLɡȈ2&İ$OU駟O<1@:[n%-QGuG"x:[BEmVȀZ^KAhC~m<1,[MXve1QBJNz(. Cc`fCط~2G4eC(38Hq> |W򥕱3sg[ղw!љ4 {/w })w&m3Gt!h&6 L)?_M#M~>.R}(4@Rn ~=?Ҿ{">0?.6bTLWE@jKqꫯ<6Cg0زXƑX0!Ր . *Q f<6ctr/Y\<V5FcUC<󜏊U'O,Qq([%["',e3>sI'XGC]`+zg%Apr!XsGP RKS0g64qȴ 2G=[~ lHx8̒^{S*d,.Za IQ2t &Lh1V lvuW%ziiR)=xh9u3<^YV@VM!duLvaf&:eO2QŒu$.! I>PUi R@P\n"JjA(hBFX>cɸ3il"Yqa 2իW/nL.3PRM8芶4^AndUW] LBL&/k,'t&Sye'ˆԐ5/JIe|fM FK;F~ԓZ 6C2`ڏI ˦A4\B0` D[7[ XS˙KJk { ,w\j1Uacy'QP> ̷j+ŕ>XjÍ,Ж|8DyR--k"vzkWW_DzY àZf`dOIh>̛axl|@2/J IBs-HTHِX,͓B,a ͝4(iA|DV3G&4 u;aȅ0|FFO&JEkz[_w8zLti"`y+ߵ|g[0#|9tJ8&VXq'27:'UF̅=cVzZF9K`"PC gkN:d\J /OhNBЙKg`XH⇿y(@+ og#">biaɨ)0np1ؖH S*cPf@L5!X 1)!l!F"%LJYt1~KH 5Fޣ]111g amwgYGX_䁽~3f3% j̘Qs=r-:{y\3 8 `b$ݱfckj ]l%**X sbږכo):2~hPн٩  6 VH#bڅpga0Q{ BV ޤCMY-T_R:?hQB#͙:%. R~nF1֐]0 Pi!4w>ɫrx(v5 >42<d)<2 Y]Tg .s&`" 9 Dc!'Ͱ/ ca[`br# ѥ|eH -<<V衃,,ehdmمeDBQp#ХԔA0_mAr$%% ɒp50l#@l\NU$"TMbDH;r DH_$'QV7ʃdz<)(f:\}>5s֝[+Y@n0dF6˿mFQ/Beݩ|-sfԱl|.#z ХLyKmʃHP2ie`of!'P/]h -)Jm u0r ϰ`j[*},0Y8 t @ `7,0o hn,fR /1ca H|4QFlLbаf2~ .y4BiV 4IxL.dI( klcu,FѴ[S7V(fX&Z_r0[&bb5hUVY%7YTRGbIfOgԦl¸٦oi8gD}HҌna0\1EAM&yj>.IUc3^6[$\9> )dp-6֚jԼ+ZQl'=cYؔyxO,-*{tJlJݗ.<)+UٯfZM4aW pن,v녺4{/&=f.qM̊][5]:ܼRT~b<(Yت*>xFJ?@s(M6ل8—s"w_zGڰMl e$ < z%?~_1EeD k8*:#]PDn5415nFl8ϔ=&@R`Ⱥ (b,釗j{ϓ0Rn$AZL~ԩ5җ4@u)v|xU4zG7 ƓzkXQ륺]{ 88m৑!H%yԏBm1<85/OJ =xa;IBM1 iʓI5L >e9DjK}+)ü?x϶ZWK2g>f%Z`9j>\y- F1]|i0h+~y1!s.e2m ɫ)%K(R p[ /y`+B1p LsURc8AљA (W mݖX ~: I |DZ>@Ylo99؂ o O 9-A3R0, Y)-oj% ɚ~fݢ'q-?#}O,k@bwni$4L'HaGx&A|Ӡ)%h}Ёth8nrn tiVԛK$%>a^UkrV2L1ḡ14u_ 7 W`kNS4V֐ץ5̨I7 5 d4" J&!"<굎T $c &T(KE=>,cLbRz" " cVX#' 9 s~qјA5FR_ m0U=TVr@{%f˯keU/D /dO&g;Dl N<ĝw޹=USuhmK}mٙ:>odeCD@D@D@w\cr8Ӌ?X[[8Nm5BI^zr@slW!DE(" RNˎ" " " " " " "PCҥ5D@D@D@D@D@D@*& ]Z12E!TR" " " " " " ."@ 6oKNq7‰@ HU|B{桬m(̓Et #ҥ1U%ʳnEJ]i0oXD@D@D}.m_ڈTDݎkw3GEwqqَ0|]x{wv_7Q7SwT_ n(q?|^~& gݼFrw\%J#E7^%qwl+O=>x;J"qs`$ks}wROD@D@ I@ͦBԀ^FtUB]Gizy$8ʳO?FnF8{oCn‰㞼? 3n"7t9^&f:hyD;N&2뵄[רTvEK y>wn%/?zrs]wa>" " "PX [r\D@D@ZA`{ݠ]T؉Ur #߹OoY곑<ן#-znԏ "&γpQl.iε{ȔUvQr/=9]`[ŧ_F'ƛ gu;ঙ!"ouG÷Ã&;G#" " $ ]ZvSE@D@ZIko"JIQz,Aѓ$ ٫%/>vKR>C?N7s˭9F;En|,Ŕ}th׻>;D{=ҺDu<kۛ/o⮵y$C:O" " "PLZ[vSE@D@ZC݇o֋&dinZ1wHp?b-|]b76҉'s3Wi!מogr3FKp1^%2xz-u#*'2SO2ON}סCx=z7i)bR=)x/Yvc3iϕ׏! #)?z?hE"og\X5tr|Ѩd1Hˇ@moxZ/-hId9mVfsZ~{6-l7ZD@D5f=:vhwۯtSO!%Z+݈.5I= Ԟ / qpp}M-~ !ۻNzDдq:[.egΊeN;q8e8zө_.-lө" " UH}Or7 pKts}sq%an&KzEVP^Dz]ePtY#{4]߭[;P>ўsAm^sNtN/1>R#Gb_+v9w.ne]W" " "PXF+jQãeQ]vEbO4TtaÆ12ܹsi(\(\ ZwAqM? 6ѯl9?r|'ݧs[2o:ڻd8hIZE-sfogE0! U &׍}5rJ\ۿ׻Trt39#" " 텀񶗖T=D@D@D@D@D@DKn*ҥ%U(&bJ-" " " " " 텀ti{iICD@D@D@D@D@I@R@{! ]^ZRb.-f" " " " " "^HT=D@D@D@D@D@DKn*ҥ%U(&bJ-" " " " " 텀ti{iICD@D@D@D@D@I@R@{! ]^ZRb.-f" " " " " "^HT=D@D@D@D@D@DKn*ҥ%U(&bJ-" " " " " 텀ti{iICD@D@D@D@D@I@R@ߟ|ߨ G@tkrUXD@D`РAc5/Uhr MꪫV]Ax3K5Ķ7d' CjIUz{vl4}.;ONofkFF;Oyᇛg ^tE??[hPfC0MeIK,q}p gwm޽;Y#/r:sI'1z~R٦'s:%Y 5O X9c9he_5hJ h\*߿?{}ESL1EϞ=wy_|UQ~_YO?=8CYfW~+z` p'-@vc$x< tBv!f|M}gG}Dve;]uUܼ:uɶ{ >nrdQ8& SQRLM=#tv~暊7s*hyuT4݀kEF lTbch6cc9/Օwy'?8뮋[k΀PB~dRƸ{ld8X7 дLw`w٣G7q/ڦ\Q[{إ:[.| fKE+ TBn_Jsy3*=2o>hվ2}g_I<-_fYHX,<}|ag 5IRfxS9딜Fr)TfZ,EEB3 '.ͨ :!j=f dL?,CҤ#0KtQǏ%s ZJ; Ak1FT-LzDZ-.MyRȃ%KNFs~/V$ᓡK`-eǴ8|U)q6DŽk-ϫ9ZYeIHi}G_ERHNyjQp:LGFؘ`{y駙y-)R/" "P,ٺׅW9]tQ[-_cE g)sύP Qdj*U}r(>pqBW^y aߏ?a"&f0Gx^fV%|a _hX Faa0_c[ Zs aD)K5mbXcO-鈕IЈaM7-TT1F*S݌C e򄎌F>E3{T&B}ŇY)l[IDATØ;7puuIfQe uVׇq6<(:5!*y&wKmcz7I`7R?X=bֹ1qSq5<%T0K '1Б䐘0pFŧ:+#_Ⱦeiix2e{EXT1b̑d@d@(׌fMeqj.-f5LypnMLa֜( θ7V 2ߒ?R=-dkҘ$J-pjj,X>dґ o0f蓗bW̓lׇUhAdcI!Iħ`ZU -klWY#&VÈec?;O e$'e\m~gfSV;yV2v&uiF%ޕ@2AAA+P?ϾYR90 O.l'DƢ[|J37G\~Ssɚς6FvO߲+> Y:50hO8ÞTˇg]7M21r5zQhb;| 8s.™#qv.N[ S:t`6"l+CZV/AřJ ?rKbG, * p(ԥ<$<)P8Sf\fV*|٦'"axJRcM81I,R+ov~@x$[l!2$/UWd諸'$+[~}$'}T9l0Yws RrN==9mLGjNƺ*J,ב1!Y0(}# tK.! $x^*,d:>waBf/eF0`YJyGvx^GdyL[eľIfXn퟿j)kBe+\-vH>t酗!Z9ʛ9k`a!83Z6oOlUYllS˞f\D~<Ѣt  cN8Zѥx)eQ_V!2 Q"( 'G:ad[xQfIx >7~(GTgAq#4٣{wp=]iG!ɩQ3DfS;bI,gfB3/Ju~:#MaKHFR~~QG4&өxVPbH3G*Ǣ_mnX*7\x>JN78oRatsFW.{э1&8+~rV`6422&9ZÀ` ľ/EEgR0x8yi2 1Du)K)u:GrL(Au0sm,y%} (xa5ʜCI Hp}pF,hK@L>c;JRJ޽ӧ_R 34ZэqecdӤi,HU˅M',H 3Y")[{#KIXfdznZMhL~X{ĴFvJ0Ϝ lN?0YdPlARlLp[<D .JҒ/-I%Im(e#R$mm'VMXo9VS_G{i,XGWMW9ͷ6>eƇJZ5q,_ń)XL]b"̮@leKϮrKˌ^F,R<:,zĆa^ĺrgl[CƊ #ЁnwN3#1$kIfhBƊ2ǀ+YZ[–f;3%/hK#3,9F!25qd]N4"jX0F`ΧX ") c!%WvbT$$ۯ.1&!" ٌnA#ASB"181ՇXbQnj[!ɀu)a,e L!eaRMOD&51z cII,Xyaf}(sʙ,O AӰJ%u~"$">XYGua?NɋU +k}D+" 4FeY Y=2Ry4NZiCXKc@a֫pe.ag$Uau)hK5/DA\||*Mh$B4<5.o~2λy+=*;#]`mGYF,5g=᫆WJKS=Y5d9,-@exӔCD@DM{]vL"I1@X 9c5* #&{'v2,d,5 l&g ;MꝻ h].6ERS6QCD@N8*' F}>Y% M,a&'Yk̤s1l+e_ ‰l6x^[ %3l/g S,"@SǴɢŮSDC6'&֙UY֗}H 644Ÿx (ƬORٵ4ƝM-jK*" " " MHEMXz !*-ZoJ8wFJ??cGPH|Op{ +ti[Oe(r&0oaNw '" " ' ]ZTE@D@kC< ܄\W8(8f_Zp*@ u[hi{ܙGx1wn'nE&iotVXmÞpӁn9Zzc>݅'pSMVحN!neZBnUo 4wL{kM hD+l1f9]eC7m6u^-?, ibwZvsrg]%q'" " cKǀFVE@D@}-.h-ܣϹ9N~z{^7um/ݤ])O#vrn7 n\ܹFε6w>c;Y*DI/=z7GR^u[{Q ZaHsA5}]xquO \QwnT^~mC$FSH)-zteD&+u}0@};=k"{oFM9u=tgdD]h)ʳT9ϭaa)rݧ" " "~ H߶UD@D@2Kߦ:O7ڭ[cwr"5;nKѶҮSEڍopv>(:h#jjDf󏖈WK(A@ͬJOӉc]"gdivAݨۯtSObb^ه![V:wt]׹?pwt|FX~J'>xSKX"" "0.YU@}tZYC{Yޛܲӻeso|(FnV?%LGExoYt9n.ݙ7Dkz- FtS ^ǹq wq^Σx[?1@ᢣUzhS׮][L;]D*s:߰aFauܹ{ ȴYoF-v;Xj m@bDEN?4q'$])Mtr'R2g'ԇCD@D@_:洵j*" "I`KN0a;rhoLYL$" " 픀ti;mXUKD@DN0ruAɊF@tLkqWD@DuNaզԈti@*1@cHUM[JRD@D@D@D@D@D 7ܨPD@D@D@D@D@DKUI&PN 4Tۯnqǧgs: 7 W`kNV4VɊ@0z&,$_O:ufiKkkS^" " " " y H%p" " " " " "  [M=*MKR8z.U)" " " " " "ti^R '" " " " " "PDQ۷(cxIENDB`dbplyr/vignettes/dbplyr.Rmd0000644000176200001440000002626314002647450015531 0ustar liggesusers--- title: "Introduction to dbplyr" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Introduction to dbplyr} %\VignetteEngine{knitr::rmarkdown} \usepackage[utf8]{inputenc} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") options(tibble.print_min = 6L, tibble.print_max = 6L, digits = 3) ``` As well as working with local in-memory data stored in data frames, dplyr also works with remote on-disk data stored in databases. This is particularly useful in two scenarios: * Your data is already in a database. * You have so much data that it does not all fit into memory simultaneously and you need to use some external storage engine. (If your data fits in memory there is no advantage to putting it in a database: it will only be slower and more frustrating.) This vignette focuses on the first scenario because it's the most common. If you're using R to do data analysis inside a company, most of the data you need probably already lives in a database (it's just a matter of figuring out which one!). However, you will learn how to load data in to a local database in order to demonstrate dplyr's database tools. At the end, I'll also give you a few pointers if you do need to set up your own database. ## Getting started To use databases with dplyr you need to first install dbplyr: ```{r, eval = FALSE} install.packages("dbplyr") ``` You'll also need to install a DBI backend package. The DBI package provides a common interface that allows dplyr to work with many different databases using the same code. DBI is automatically installed with dbplyr, but you need to install a specific backend for the database that you want to connect to. Five commonly used backends are: * [RMariaDB](https://CRAN.R-project.org/package=RMariaDB) connects to MySQL and MariaDB * [RPostgres](https://CRAN.R-project.org/package=RPostgres) connects to Postgres and Redshift. * [RSQLite](https://github.com/r-dbi/RSQLite) embeds a SQLite database. * [odbc](https://github.com/r-dbi/odbc#odbc) connects to many commercial databases via the open database connectivity protocol. * [bigrquery](https://github.com/r-dbi/bigrquery) connects to Google's BigQuery. If the database you need to connect to is not listed here, you'll need to do some investigation (i.e. googling) yourself. In this vignette, we're going to use the RSQLite backend which is automatically installed when you install dbplyr. SQLite is a great way to get started with databases because it's completely embedded inside an R package. Unlike most other systems, you don't need to setup a separate database server. SQLite is great for demos, but is surprisingly powerful, and with a little practice you can use it to easily work with many gigabytes of data. ## Connecting to the database To work with a database in dplyr, you must first connect to it, using `DBI::dbConnect()`. We're not going to go into the details of the DBI package here, but it's the foundation upon which dbplyr is built. You'll need to learn more about if you need to do things to the database that are beyond the scope of dplyr. ```{r setup, message = FALSE} library(dplyr) con <- DBI::dbConnect(RSQLite::SQLite(), dbname = ":memory:") ``` The arguments to `DBI::dbConnect()` vary from database to database, but the first argument is always the database backend. It's `RSQLite::SQLite()` for RSQLite, `RMariaDB::MariaDB()` for RMariaDB, `RPostgres::Postgres()` for RPostgres, `odbc::odbc()` for odbc, and `bigrquery::bigquery()` for BigQuery. SQLite only needs one other argument: the path to the database. Here we use the special string `":memory:"` which causes SQLite to make a temporary in-memory database. Most existing databases don't live in a file, but instead live on another server. That means in real-life that your code will look more like this: ```{r, eval = FALSE} con <- DBI::dbConnect(RMariaDB::MariaDB(), host = "database.rstudio.com", user = "hadley", password = rstudioapi::askForPassword("Database password") ) ``` (If you're not using RStudio, you'll need some other way to securely retrieve your password. You should never record it in your analysis scripts or type it into the console. [Securing Credentials](https://db.rstudio.com/best-practices/managing-credentials) provides some best practices.) Our temporary database has no data in it, so we'll start by copying over `nycflights13::flights` using the convenient `copy_to()` function. This is a quick and dirty way of getting data into a database and is useful primarily for demos and other small jobs. ```{r} copy_to(con, nycflights13::flights, "flights", temporary = FALSE, indexes = list( c("year", "month", "day"), "carrier", "tailnum", "dest" ) ) ``` As you can see, the `copy_to()` operation has an additional argument that allows you to supply indexes for the table. Here we set up indexes that will allow us to quickly process the data by day, carrier, plane, and destination. Creating the right indices is key to good database performance, but is unfortunately beyond the scope of this article. Now that we've copied the data, we can use `tbl()` to take a reference to it: ```{r} flights_db <- tbl(con, "flights") ``` When you print it out, you'll notice that it mostly looks like a regular tibble: ```{r} flights_db ``` The main difference is that you can see that it's a remote source in a SQLite database. ## Generating queries To interact with a database you usually use SQL, the Structured Query Language. SQL is over 40 years old, and is used by pretty much every database in existence. The goal of dbplyr is to automatically generate SQL for you so that you're not forced to use it. However, SQL is a very large language and dbplyr doesn't do everything. It focusses on `SELECT` statements, the SQL you write most often as an analyst. Most of the time you don't need to know anything about SQL, and you can continue to use the dplyr verbs that you're already familiar with: ```{r} flights_db %>% select(year:day, dep_delay, arr_delay) flights_db %>% filter(dep_delay > 240) flights_db %>% group_by(dest) %>% summarise(delay = mean(dep_time)) ``` However, in the long-run, I highly recommend you at least learn the basics of SQL. It's a valuable skill for any data scientist, and it will help you debug problems if you run into problems with dplyr's automatic translation. If you're completely new to SQL you might start with this [codeacademy tutorial](https://www.codecademy.com/learn/learn-sql). If you have some familiarity with SQL and you'd like to learn more, I found [how indexes work in SQLite](https://www.sqlite.org/queryplanner.html) and [10 easy steps to a complete understanding of SQL](https://blog.jooq.org/2016/03/17/10-easy-steps-to-a-complete-understanding-of-sql/) to be particularly helpful. The most important difference between ordinary data frames and remote database queries is that your R code is translated into SQL and executed in the database on the remote server, not in R on your local machine. When working with databases, dplyr tries to be as lazy as possible: * It never pulls data into R unless you explicitly ask for it. * It delays doing any work until the last possible moment: it collects together everything you want to do and then sends it to the database in one step. For example, take the following code: ```{r} tailnum_delay_db <- flights_db %>% group_by(tailnum) %>% summarise( delay = mean(arr_delay), n = n() ) %>% arrange(desc(delay)) %>% filter(n > 100) ``` Surprisingly, this sequence of operations never touches the database. It's not until you ask for the data (e.g. by printing `tailnum_delay`) that dplyr generates the SQL and requests the results from the database. Even then it tries to do as little work as possible and only pulls down a few rows. ```{r} tailnum_delay_db ``` Behind the scenes, dplyr is translating your R code into SQL. You can see the SQL it's generating with `show_query()`: ```{r} tailnum_delay_db %>% show_query() ``` If you're familiar with SQL, this probably isn't exactly what you'd write by hand, but it does the job. You can learn more about the SQL translation in `vignette("translation-verb")` and `vignette("translation-function")`. Typically, you'll iterate a few times before you figure out what data you need from the database. Once you've figured it out, use `collect()` to pull all the data down into a local tibble: ```{r} tailnum_delay <- tailnum_delay_db %>% collect() tailnum_delay ``` `collect()` requires that database does some work, so it may take a long time to complete. Otherwise, dplyr tries to prevent you from accidentally performing expensive query operations: * Because there's generally no way to determine how many rows a query will return unless you actually run it, `nrow()` is always `NA`. * Because you can't find the last few rows without executing the whole query, you can't use `tail()`. ```{r, error = TRUE} nrow(tailnum_delay_db) tail(tailnum_delay_db) ``` You can also ask the database how it plans to execute the query with `explain()`. The output is database dependent, and can be esoteric, but learning a bit about it can be very useful because it helps you understand if the database can execute the query efficiently, or if you need to create new indices. ## Creating your own database If you don't already have a database, here's some advice from my experiences setting up and running all of them. SQLite is by far the easiest to get started with. PostgreSQL is not too much harder to use and has a wide range of built-in functions. In my opinion, you shouldn't bother with MySQL/MariaDB: it's a pain to set up, the documentation is subpar, and it's less featureful than Postgres. Google BigQuery might be a good fit if you have very large data, or if you're willing to pay (a small amount of) money to someone who'll look after your database. All of these databases follow a client-server model - a computer that connects to the database and the computer that is running the database (the two may be one and the same but usually isn't). Getting one of these databases up and running is beyond the scope of this article, but there are plenty of tutorials available on the web. ### MySQL/MariaDB In terms of functionality, MySQL lies somewhere between SQLite and PostgreSQL. It provides a wider range of [built-in functions](http://dev.mysql.com/doc/refman/5.0/en/functions.html). It gained support for window functions in 2018. ### PostgreSQL PostgreSQL is a considerably more powerful database than SQLite. It has a much wider range of [built-in functions](http://www.postgresql.org/docs/current/static/functions.html), and is generally a more featureful database. ### BigQuery BigQuery is a hosted database server provided by Google. To connect, you need to provide your `project`, `dataset` and optionally a project for `billing` (if billing for `project` isn't enabled). It provides a similar set of functions to Postgres and is designed specifically for analytic workflows. Because it's a hosted solution, there's no setup involved, but if you have a lot of data, getting it to Google can be an ordeal (especially because upload support from R is not great currently). (If you have lots of data, you can [ship hard drives](https://cloud.google.com/products/data-transfer)!) dbplyr/vignettes/reprex.Rmd0000644000176200001440000000564014002647450015536 0ustar liggesusers--- title: "Reprexes for dbplyr" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Reprexes for dbplyr} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` If you're reporting a bug in dbplyr, it is much easier for me to help you if you can supply a [reprex](https://reprex.tidyverse.org) that I can run on my computer. Creating reprexes for dbplyr is particularly challenging because you are probably using a database that you can't share with me. Fortunately, in many cases you can still demonstrate the problem even if I don't have the complete dataset, or even access to the database system that you're using. This vignette outlines three approaches for creating reprexes that will work anywhere: * Use `memdb_frame()`/`tbl_memdb()` to easily create datasets that live in an in-memory SQLite database. * Use `lazy_frame()`/`tbl_lazy()` to simulate SQL generation of dplyr pipelines. * Use `translate_sql()` to simulate SQL generation of columnar expression. ```{r setup, message = FALSE} library(dplyr) library(dbplyr) ``` ## Using `memdb_frame()` The first place to start is with SQLite. SQLite is particularly appealing because it's completely embedded instead an R package so doesn't have any external dependencies. SQLite is designed to be small and simple, so it can't demonstrate all problems, but it's easy to try out and a great place to start. You can easily create a SQLite in-memory database table using `memdb_frame()`: ```{r} mf <- memdb_frame(g = c(1, 1, 2, 2, 2), x = 1:5, y = 5:1) mf mf %>% group_by(g) %>% summarise_all(mean, na.rm = TRUE) ``` Reprexes are easiest to understand if you create very small custom data, but if you do want to use an existing data frame you can use `tbl_memdb()`: ```{r} mtcars_db <- tbl_memdb(mtcars) mtcars_db %>% group_by(cyl) %>% summarise(n = n()) %>% show_query() ``` ## Translating verbs Many problems with dbplyr come down to incorrect SQL generation. Fortunately, it's possible to generate SQL without a database using `lazy_frame()` and `tbl_lazy()`. Both take an `con` argument which takes a database "simulator" like `simulate_postgres()`, `simulate_sqlite()`, etc. ```{r} x <- c("abc", "def", "ghif") lazy_frame(x = x, con = simulate_postgres()) %>% head(5) %>% show_query() lazy_frame(x = x, con = simulate_mssql()) %>% head(5) %>% show_query() ``` If you isolate the problem to incorrect SQL generation, it would be very helpful if you could also suggest more appropriate SQL. ## Translating individual expressions In some cases, you might be able to track the problem down to incorrect translation for a single column expression. In that case, you can make your reprex even simpler with `translate_sql()`: ```{r} translate_sql(substr(x, 1, 2), con = simulate_postgres()) translate_sql(substr(x, 1, 2), con = simulate_sqlite()) ``` dbplyr/vignettes/translation-function.Rmd0000644000176200001440000002316714002647450020416 0ustar liggesusers--- title: "Function translation" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Function translation} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") options(tibble.print_min = 4L, tibble.print_max = 4L) ``` There are two parts to dbplyr SQL translation: translating dplyr verbs, and translating expressions within those verbs. This vignette describes how individual expressions (function calls) are translated; `vignette("translate-verb")` describes how entire verbs are translated. ```{r, message = FALSE} library(dbplyr) library(dplyr) ``` `dbplyr::translate_sql()` powers translation of individual function calls, and I'll use it extensively in this vignette to show what's happening. You shouldn't need to use it ordinary code as dbplyr takes care of the translation automatically. ```{r} translate_sql((x + y) / 2) ``` `translate_sql()` takes an optional `con` parameter. If not supplied, this causes dplyr to generate (approximately) SQL-92 compliant SQL. If supplied, dplyr uses `sql_translate_env()` to look up a custom environment which makes it possible for different databases to generate slightly different SQL: see `vignette("new-backend")` for more details. You can use the various simulate helpers to see the translations used by different backends: ```{r} translate_sql(x ^ 2L) translate_sql(x ^ 2L, con = simulate_sqlite()) translate_sql(x ^ 2L, con = simulate_access()) ``` Perfect translation is not possible because databases don't have all the functions that R does. The goal of dplyr is to provide a semantic rather than a literal translation: what you mean, rather than precisely what is done. In fact, even for functions that exist both in databases and R, you shouldn't expect results to be identical; database programmers have different priorities than R core programmers. For example, in R in order to get a higher level of numerical accuracy, `mean()` loops through the data twice. R's `mean()` also provides a `trim` option for computing trimmed means; this is something that databases do not provide. If you're interested in how `translate_sql()` is implemented, the basic techniques that underlie the implementation of `translate_sql()` are described in ["Advanced R"](https://adv-r.hadley.nz/translation.html). ## Basic differences The following examples work through some of the basic differences between R and SQL. * `"` and `'` mean different things ```{r} # In SQLite variable names are escaped by double quotes: translate_sql(x) # And strings are escaped by single quotes translate_sql("x") ``` * And some functions have different argument orders: ```{r} translate_sql(substr(x, 5, 10)) translate_sql(log(x, 10)) ``` * R and SQL have different defaults for integers and reals. In R, 1 is a real, and 1L is an integer. In SQL, 1 is an integer, and 1.0 is a real ```{r} translate_sql(1) translate_sql(1L) ``` ## Known functions ### Mathematics * basic math operators: `+`, `-`, `*`, `/`, `^` * trigonometry: `acos()`, `asin()`, `atan()`, `atan2()`, `cos()`, `cot()`, `tan()`, `sin()` * hypergeometric: `cosh()`, `coth()`, `sinh()`, `tanh()` * logarithmic: `log()`, `log10()`, `exp()` * misc: `abs()`, `ceiling()`, `sqrt()`, `sign()`, `round()` ## Modulo arithmetic dbplyr translates `%%` to the SQL equivalents but note that it's not precisely the same: most databases use truncated division where the modulo operator takes the sign of the dividend, where R using the mathematically preferred floored division with the modulo sign taking the sign of the divisor. ```{r} df <- tibble( x = c(10L, 10L, -10L, -10L), y = c(3L, -3L, 3L, -3L) ) mf <- tbl_memdb(df) df %>% mutate(x %% y) mf %>% mutate(x %% y) ``` dbplyr no longer translates `%/%` because there's no robust cross-database translation available. ### Logical comparisons * logical comparisons: `<`, `<=`, `!=`, `>=`, `>`, `==`, `%in%` * boolean operations: `&`, `&&`, `|`, `||`, `!`, `xor()` ### Aggregation All database provide translation for the basic aggregations: `mean()`, `sum()`, `min()`, `max()`, `sd()`, `var()`. Databases automatically drop NULLs (their equivalent of missing values) whereas in R you have to ask nicely. The aggregation functions warn you about this important difference: ```{r} translate_sql(mean(x)) translate_sql(mean(x, na.rm = TRUE)) ``` Note that, by default, `translate()` assumes that the call is inside a `mutate()` or `filter()` and generates a window translation. If you want to see the equivalent `summarise()`/aggregation translation, use `window = FALSE`: ```{r} translate_sql(mean(x, na.rm = TRUE), window = FALSE) ``` ### Conditional evaluation `if` and `switch()` are translate to `CASE WHEN`: ```{r} translate_sql(if (x > 5) "big" else "small") translate_sql(switch(x, a = 1L, b = 2L, 3L)) ``` ### String manipulation ### Date/time * string functions: `tolower`, `toupper`, `trimws`, `nchar`, `substr` * coerce types: `as.numeric`, `as.integer`, `as.character` ## Unknown functions Any function that dplyr doesn't know how to convert is left as is. This means that database functions that are not covered by dplyr can be used directly via `translate_sql()`. Here a couple of examples that will work with [SQLite](https://www.sqlite.org/lang_corefunc.html): ```{r} translate_sql(glob(x, y)) translate_sql(x %like% "ab%") ``` See `vignette("sql")` for more details. ## Window functions Things get a little trickier with window functions, because SQL's window functions are considerably more expressive than the specific variants provided by base R or dplyr. They have the form `[expression] OVER ([partition clause] [order clause] [frame_clause])`: * The __expression__ is a combination of variable names and window functions. Support for window functions varies from database to database, but most support the ranking functions, `lead`, `lag`, `nth`, `first`, `last`, `count`, `min`, `max`, `sum`, `avg` and `stddev`. * The __partition clause__ specifies how the window function is broken down over groups. It plays an analogous role to `GROUP BY` for aggregate functions, and `group_by()` in dplyr. It is possible for different window functions to be partitioned into different groups, but not all databases support it, and neither does dplyr. * The __order clause__ controls the ordering (when it makes a difference). This is important for the ranking functions since it specifies which variables to rank by, but it's also needed for cumulative functions and lead. Whenever you're thinking about before and after in SQL, you must always tell it which variable defines the order. If the order clause is missing when needed, some databases fail with an error message while others return non-deterministic results. * The __frame clause__ defines which rows, or __frame__, that are passed to the window function, describing which rows (relative to the current row) should be included. The frame clause provides two offsets which determine the start and end of frame. There are three special values: -Inf means to include all preceding rows (in SQL, "unbounded preceding"), 0 means the current row ("current row"), and Inf means all following rows ("unbounded following"). The complete set of options is comprehensive, but fairly confusing, and is summarised visually below. ```{r echo = FALSE, out.width = "100%"} knitr::include_graphics("windows.png", dpi = 300) ``` Of the many possible specifications, there are only three that commonly used. They select between aggregation variants: * Recycled: `BETWEEN UNBOUND PRECEEDING AND UNBOUND FOLLOWING` * Cumulative: `BETWEEN UNBOUND PRECEEDING AND CURRENT ROW` * Rolling: `BETWEEN 2 PRECEEDING AND 2 FOLLOWING` dplyr generates the frame clause based on whether your using a recycled aggregate or a cumulative aggregate. To see how individual window functions are translated to SQL, we can again use `translate_sql()`: ```{r} translate_sql(mean(G)) translate_sql(rank(G)) translate_sql(ntile(G, 2)) translate_sql(lag(G)) ``` If the tbl has been grouped or arranged previously in the pipeline, then dplyr will use that information to set the "partition by" and "order by" clauses. For interactive exploration, you can achieve the same effect by setting the `vars_group` and `vars_order` arguments to `translate_sql()` ```{r} translate_sql(cummean(G), vars_order = "year") translate_sql(rank(), vars_group = "ID") ``` There are some challenges when translating window functions between R and SQL, because dplyr tries to keep the window functions as similar as possible to both the existing R analogues and to the SQL functions. This means that there are three ways to control the order clause depending on which window function you're using: * For ranking functions, the ordering variable is the first argument: `rank(x)`, `ntile(y, 2)`. If omitted or `NULL`, will use the default ordering associated with the tbl (as set by `arrange()`). * Accumulating aggregates only take a single argument (the vector to aggregate). To control ordering, use `order_by()`. * Aggregates implemented in dplyr (`lead`, `lag`, `nth_value`, `first_value`, `last_value`) have an `order_by` argument. Supply it to override the default ordering. The three options are illustrated in the snippet below: ```{r, eval = FALSE} mutate(players, min_rank(yearID), order_by(yearID, cumsum(G)), lead(G, order_by = yearID) ) ``` Currently there is no way to order by multiple variables, except by setting the default ordering with `arrange()`. This will be added in a future release. dbplyr/vignettes/backend-2.Rmd0000644000176200001440000001042114031447261015750 0ustar liggesusers--- title: "dplyr 2.0.0 backend API" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{dplyr 2.0.0 backend API} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` This transition guide is aimed at backend authors. dbplyr 2.0.0 is an important release for backends because it starts the process of moving all backend generics into dbplyr (instead of some living in dplyr). This move has been designed to occur in phases to avoid sudden breakages and give backend authors plenty of time to make changes. The current timeline is something like this: - dbplyr 2.0.0 adds a new interface for database backends. The old interface remains so all existing backends continue to work, but new packages should use the new interface, and existing backends should start the update process.  - dbplyr 2.1.0 (to be released \>= 6 months after dbplyr 2.0.0) deprecates the old interface, so that users are encouraged to upgrade backends. - dbplyr 2.2.0 (to be released \>= 12 months after dbplyr 2.0.0) removes the old interface so user must upgrade backends. - A future version of dplyr will deprecate then remove the database generics. ## Unused generics A number of generics are no longer used so you can delete the corresponding methods: - `db_write_table()` calls `DBI::dbWriteTable()` instead of nine individual generics: `db_create_indexes()`, `db_begin()`, `db_rollback()`, `db_commit()`, `db_list_tables()`, `db_drop_table()`, `db_has_table()`, `db_create_table()`, and `db_data_types()`. - `sql_escape_ident()` and `sql_escape_string()` are no longer used in favour of calling `dbQuoteIdentifier()` and `dbQuoteString()` directly. - `db_query_rows()` was never actually used. Making these changes are important because they ensure your backend works consistently whether you use it through DBI or dplyr. ## 2nd edition dbplyr 2.0.0 draws inspiration from the idea of an [edition](https://testthat.r-lib.org/articles/third-edition.html) so that to tell dbplyr to use the new generics, you need to do two things: - Depend on dbplyr 2.0.0 in your `DESCRIPTION`, e.g. `Imports: dbplyr (>= 2.0.0)`. This ensures that when someone installs your package they get the latest version of dbplyr. - Provide a method for the `dbplyr_edition` generic: ```{r} #' @importFrom dbplyr dbplyr_edition #' @export dbplyr_edition.myConnectionClass <- function(con) 2L ``` This tells dbplyr to use the new generics instead of the old generics. Then you'll need to update your methods, following the advice below. ## SQL generation There are a number of dplyr generics that generate then execute SQL. These have been replaced by dbplyr generics that just generate the SQL (and dbplyr takes care of executing it): - `dplyr::db_analyze()` -\> `dbplyr::sql_table_analyze()` - `dplyr::db_create_index()` -\> `dbplyr::sql_table_index()` - `dplyr::db_explain()` -\> `dbplyr::sql_query_explain()` - `dplyr::db_query_fields()` -\> `dbplyr::sql_query_fields()` - `dplyr::db_save_query()` -\> `dbplyr::sql_query_save()` If you have methods for any of those generics, you'll need to extract the SQL generation code into a new `sql_` method. ## Renamed generics A number of other generics have been renamed: - `dplyr::sql_select()` -\> `dbplyr::sql_query_select()` - `dplyr::sql_join()` -\> `dbplyr::sql_query_join()` - `dplyr::sql_semi_join()` -\> `dbplyr::sql_query_semi_join()` - `dplyr::sql_set_op()` -\> `dbplyr::sql_query_set_op()` - `dplyr::sql_subquery()` -\> `dbplyr::sql_query_wrap()` - `dplyr::sql_translate_env()` -\> `dbplyr::sql_translation()` - `dplyr::db_desc()` -\> `dbplyr::db_connection_describe()` If you have methods for any of these generics, you'll need to rename. ## New generics You may also want to consider methods for the new generics in dbplyr 2.0.0: - Provide a method for `db_temporary_table()` if your backend requires that temporary tables have special names. - Provide a method for `sql_expr_matches()` if your database has special syntax for matching two values (see ). - Provide a method for `sql_join_suffix()` if your backend can't use the usual `.x` and `.y` suffixes in joins. dbplyr/vignettes/translation-verb.Rmd0000644000176200001440000000772014002647450017524 0ustar liggesusers--- title: "Verb translation" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Verb translation} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- There are two parts to dbplyr SQL translation: translating dplyr verbs, and translating expressions within those verbs. This vignette describes how entire verbs are translated; `vignette("translate-function")` describes how individual expressions within those verbs are translated. All dplyr verbs generate a `SELECT` statement. To demonstrate we'll make a temporary database with a couple of tables ```{r, message = FALSE} library(dplyr) con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") flights <- copy_to(con, nycflights13::flights) airports <- copy_to(con, nycflights13::airports) ``` ## Single table verbs * `select()` and `mutate()` modify the `SELECT` clause: ```{r} flights %>% select(contains("delay")) %>% show_query() flights %>% select(distance, air_time) %>% mutate(speed = distance / (air_time / 60)) %>% show_query() ``` (As you can see here, the generated SQL isn't always as minimal as you might generate by hand.) * `filter()` generates a `WHERE` clause: ```{r} flights %>% filter(month == 1, day == 1) %>% show_query() ``` * `arrange()` generates an `ORDER BY` clause: ```{r} flights %>% arrange(carrier, desc(arr_delay)) %>% show_query() ``` * `summarise()` and `group_by()` work together to generate a `GROUP BY` clause: ```{r} flights %>% group_by(month, day) %>% summarise(delay = mean(dep_delay)) %>% show_query() ``` ## Dual table verbs | R | SQL |------------------|------------------------------------------------------------ | `inner_join()` | `SELECT * FROM x JOIN y ON x.a = y.a` | `left_join()` | `SELECT * FROM x LEFT JOIN y ON x.a = y.a` | `right_join()` | `SELECT * FROM x RIGHT JOIN y ON x.a = y.a` | `full_join()` | `SELECT * FROM x FULL JOIN y ON x.a = y.a` | `semi_join()` | `SELECT * FROM x WHERE EXISTS (SELECT 1 FROM y WHERE x.a = y.a)` | `anti_join()` | `SELECT * FROM x WHERE NOT EXISTS (SELECT 1 FROM y WHERE x.a = y.a)` | `intersect(x, y)`| `SELECT * FROM x INTERSECT SELECT * FROM y` | `union(x, y)` | `SELECT * FROM x UNION SELECT * FROM y` | `setdiff(x, y)` | `SELECT * FROM x EXCEPT SELECT * FROM y` `x` and `y` don't have to be tables in the same database. If you specify `copy = TRUE`, dplyr will copy the `y` table into the same location as the `x` variable. This is useful if you've downloaded a summarised dataset and determined a subset of interest that you now want the full data for. You can use `semi_join(x, y, copy = TRUE)` to upload the indices of interest to a temporary table in the same database as `x`, and then perform a efficient semi join in the database. If you're working with large data, it maybe also be helpful to set `auto_index = TRUE`. That will automatically add an index on the join variables to the temporary table. ## Behind the scenes The verb level SQL translation is implemented on top of `tbl_lazy`, which basically tracks the operations you perform in a pipeline (see `lazy-ops.R`). Turning that into a SQL query takes place in three steps: * `sql_build()` recurses over the lazy op data structure building up query objects (`select_query()`, `join_query()`, `set_op_query()` etc) that represent the different subtypes of `SELECT` queries that we might generate. * `sql_optimise()` takes a pass over these SQL objects, looking for potential optimisations. Currently this only involves removing subqueries where possible. * `sql_render()` calls an SQL generation function (`sql_query_select()`, `sql_query_join()`, `sql_query_semi_join()`, `sql_query_set_op()`, ...) to produce the actual SQL. Each of these functions is a generic, taking the connection as an argument, so that the translation can be customised for different databases. dbplyr/vignettes/windows.graffle0000644000176200001440000000573413415745770016626 0ustar liggesusers][w~N~'`NYΥmz$S;i_m&\zߏ*޺[Hÿ< #7_'!ܻ::=I4K7'A=hvONW"i4g{Fc:ʐJv0Q: (ij Ql !WG;hvԶ] }}=lЫc4@rN3kCL=KMEV,뱈"DL51D>ēzIq "!<%E!d<q IXәeL܊=kH.\ղ &?f & sp{ɨ3Y)/jz0,ܱUdPF*=xt4K*vHk+B9!ƃcyԨ%蝚%:tGeofц$

3;S7-[PbF&qFpfc3SpoѴn݋۞&sކ* ޚ <ݺ&P+M;JHȑ$EN6__y;;LZSQ)R6e>.Qxbb! 5nxF7eӻpksbl#FmT`%7 Aσ>c)I-Z2O,s0d-LjƑ%^8zg鎙G:x8Pq\RËՌԝkF(b|S8xŃyu Ŀݔ:{^R^a_V8}W2H&!p>3<%-ܗl$unH, h"tg{z&VSӥ:jx M]VM9ֳ"`}01( rCg|,@. o/ֈmUr[}vVa8xv[BludCӒud{6`  ba|ïJCdH6C#hi\ܠm h ho94sX$}^#H0ܐ- do#{}[2o]_Cd KegX_$_̪Jo_:ʐ(otػUF 2 CgjR5j\5.dVQ"Կ vlgtfv:xX҈?p iPP9FZ@A@aà`< F36hs%$i@ 2b5  lO  †Az8aM4?*`>D, FARTQD<:ͥ6 D~(0PPx*ӯeμi" q/c̍f'<yr1ɵ\q6Z&?o6M u[mwp#tng. ' mSVY?m~l/nsA_nI&8h;L" p8#)g)r w$^9Z\`sjawp2K\Qf4},gpFcʥɪ/#DK+.B((̒Uc19A)H6,f>3 S/Unvtc_q{$)J u #Ҳ!OW6.敆Cb֍O?@V:8nߵf>UIƪځU^h \a8!gk㡙tC8*k]8& JHr9Sg;:&=~0/; Yzg!K{Vu޼>ު ߱M>s6lnC}ك۹Q? |ə킷?^*۳v3yuMO^ܡ&@}}c*9.ΜIl\޻6N7oOd|{p/C+_YIV Y;#tr2^J"(%V&![v2 j3!|7b^Z֭;Ov/w6FaU)WډFID:GJcW!O1oNx PHmiZqI)Ş=! \ %7],F ߚEwTSrsKA1s#)dbplyr/vignettes/new-backend.Rmd0000644000176200001440000000641714002647450016412 0ustar liggesusers--- title: "Adding a new DBI backend" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Adding a new DBI backend} %\VignetteEngine{knitr::rmarkdown} \usepackage[utf8]{inputenc} --- ```{r, echo = FALSE, message = FALSE} knitr::opts_chunk$set(collapse = T, comment = "#>") options(tibble.print_min = 4L, tibble.print_max = 4L) ``` This document describes how to add a new SQL backend to dbplyr. To begin: * Ensure that you have a DBI compliant database backend. If not, you'll need to first create it by following the instructions in `vignette("backend", package = "DBI")`. * You'll need a working knowledge of S3. Make sure that you're [familiar with the basics](https://adv-r.hadley.nz/s3.html) before you start. This document is still a work in progress, but it will hopefully get you started. I'd also strongly recommend reading the bundled source code for [SQLite](https://github.com/tidyverse/dbplyr/blob/master/R/backend-sqlite.R), [MySQL](https://github.com/tidyverse/dbplyr/blob/master/R/backend-mysql.R), and [PostgreSQL](https://github.com/tidyverse/dbplyr/blob/master/R/backend-postgres.R). ## First steps For interactive exploitation, attach dplyr and DBI. If you're creating a package, you'll need to import dplyr and DBI. ```{r setup, message = FALSE} library(dplyr) library(DBI) ``` Check that you can create a tbl from a connection, like: ```{r} con <- DBI::dbConnect(RSQLite::SQLite(), path = ":memory:") DBI::dbWriteTable(con, "mtcars", mtcars) tbl(con, "mtcars") ``` If you can't, this likely indicates some problem with the DBI methods. Use [DBItest](https://github.com/r-dbi/DBItest) to narrow down the problem. ## Write your first method The first method of your dbplyr backend should always be for the `dbplyr_edition()` generic: ```{r} #' @importFrom dbplyr dbplyr_edition #' @export dbplyr_edition.myConnectionClass <- function(con) 2L ``` This declares that your package uses version 2 of the API, which is the version that this vignette documents. ## Copying, computing, collecting and collapsing Next, check that `copy_to()`, `collapse()`, `compute()`, and `collect()` work: * If `copy_to()` fails, you probably need a method for `sql_table_analyze()` or `sql_table_index()`. If `copy_to()` fails during creation of the tbl, you may need a method for `sql_query_fields()`. * If `collapse()` fails, your database has a non-standard way of constructing subqueries. Add a method for `sql_subquery()`. * If `compute()` fails, your database has a non-standard way of saving queries in temporary tables. Add a method for `db_save_query()`. ## SQL translation Make sure you've read `vignette("translation-verb")` so you have the lay of the land. ### Verbs Check that SQL translation for the key verbs work: * `summarise()`, `mutate()`, `filter()` etc: powered by `sql_query_select()` * `left_join()`, `inner_join()`: powered by `sql_query_join()` * `semi_join()`, `anti_join()`: powered by `sql_query_semi_join()` * `union()`, `intersect()`, `setdiff()`: powered by `sql_query_set_op()` ### Vectors Finally, you may have to provide custom R -> SQL translation at the vector level by providing a method for `sql_translate_env()`. This function should return an object created by `sql_variant()`. See existing methods for examples. dbplyr/vignettes/sql.Rmd0000644000176200001440000000603513474056125015034 0ustar liggesusers--- title: "Writing SQL with dbplyr" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Writing SQL with dbplyr} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` This vignette discusses why you might use dbplyr instead of writing SQL yourself, and what to do when dbplyr's built-in translations can't create the SQL that you need. ```{r setup, message = FALSE} library(dplyr) library(dbplyr) mf <- memdb_frame(x = 1, y = 2) ``` ## Why use dbplyr? One simple nicety of dplyr is that it will automatically generate subqueries if you want to use a freshly created variable in `mutate()`: ```{r} mf %>% mutate( a = y * x, b = a ^ 2, ) %>% show_query() ``` In general, it's much easier to work iteratively in dbplyr. You can easily give intermediate queries names, and reuse them in multiple places. Or if you have a common operation that you want to do to many queries, you can easily wrap it up in a function. It's also easy to chain `count()` to the end of any query to check the results are about what you expect. ## What happens when dbplyr fails? dbplyr aims to translate the most common R functions to their SQL equivalents, allowing you to ignore the vagaries of the SQL dialect that you're working with, so you can focus on the data analysis problem at hand. But different backends have different capabilities, and sometimes there are SQL functions that don't have exact equivalents in R. In those cases, you'll need to write SQL code directly. This section shows you how you can do so. ### Prefix functions Any function that dbplyr doesn't know about will be left as is: ```{r} mf %>% mutate(z = foofify(x, y)) %>% show_query() ``` Because SQL functions are general case insensitive, I recommend using upper case when you're using SQL functions in R code. That makes it easier to spot that you're doing something unusual: ```{r} mf %>% mutate(z = FOOFIFY(x, y)) %>% show_query() ``` ### Infix functions As well as prefix functions (where the name of the function comes before the arguments), dbplyr also translates infix functions. That allows you to use expressions like `LIKE` which does a limited form of pattern matching: ```{r} mf %>% filter(x %LIKE% "%foo%") %>% show_query() ``` Or use `||` for string concatenation (note that backends should translate `paste()` and `paste0()` for you): ```{r} mf %>% transmute(z = x %||% y) %>% show_query() ``` ### Special forms SQL functions tend to have a greater variety of syntax than R. That means there are a number of expressions that can't be translated directly from R code. To insert these in your own queries, you can use literal SQL inside `sql()`: ```{r} mf %>% transmute(factorial = sql("x!")) %>% show_query() mf %>% transmute(factorial = sql("CAST(x AS FLOAT)")) %>% show_query() ``` Note that you can use `sql()` at any depth inside the expression: ```{r} mf %>% filter(x == sql("ANY VALUES(1, 2, 3)")) %>% show_query() ``` dbplyr/vignettes/setup/0000755000176200001440000000000014002647450014720 5ustar liggesusersdbplyr/vignettes/setup/_mysql.Rmd0000644000176200001440000000117214002647450016671 0ustar liggesusers# MariaDB ## Install ``` brew install mariadb mysql_install_db --verbose --user=hadley --basedir=/usr/local \ --datadir=/User/hadley/db/mariadb --tmpdir=/tmp mysqld --datadir='/Users/hadley/db/mysql' mysql -u root -e "CREATE DATABASE lahman;" mysql -u root -e "CREATE DATABASE nycflights13;" mysql -u root -e "CREATE DATABASE test;" ``` ## Start ``` mysqld --datadir='/Users/hadley/db/mysql' ``` ## Connect ```{r, eval = FALSE} install.packages("RMariaDB") con <- dbConnect(RMariaDB::MariaDB(), dbname = "lahman", username = "root", password = "") dbListTables(con) ``` # Shut down ``` mysqladmin shutdown -u root -p ``` dbplyr/vignettes/setup/_sap-hana.Rmd0000644000176200001440000000230214002647450017210 0ustar liggesusers# SAP HANA ## Sign up for free cloud trial * * Go to * Click trial, then "dev" space. * Then follow * Allow access from all IP addresses. * Need to restart service every day; it's automatically stopped each night. * Need to create `test` database using database explorer ## Install drivers * Download SAP HANA installer from * Uncheck virtual machine; check "clients (mac)" * Untar download * Untar `hdb_client_mac.tgz` * `./hdb_inst` Configure odbc.init: `edit_file("/usr/local/etc/odbcinst.ini")` ``` [SAP HANA] Description=SAP HANA Driver=/Applications/sap/hdbclient/libodbcHDB.dylib ``` Check: ```{r} odbc::odbcListDrivers() ``` ## Connect ```{r, eval = FALSE} con <- DBI::dbConnect(odbc::odbc(), driver = "SAP HANA", uid = "DBADMIN", pwd = "Password12", servernode = "a9441502-7166-4a5a-b5d8-23abe3b5009c.hana.trial-us10.hanacloud.ondemand.com:443", encrypt="true", sslValidateCertificate = "false" ) class(con) #> [1] "HDB" ``` dbplyr/vignettes/setup/_mssql.Rmd0000644000176200001440000000127314002647450016665 0ustar liggesusers# MySQL ## Install drivers ``` brew tap microsoft/mssql-release https://github.com/Microsoft/homebrew-mssql-release brew update brew install msodbcsql17 mssql-tools ``` Check available: ```{r} odbc::odbcListDrivers() edit_file("/usr/local/etc/odbcinst.ini") Sys.setenv("GITHUB_MSSQL" = "true") ``` ## Start ``` docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password12' -t -i -p 1433:1433 microsoft/mssql-server-linux sqlcmd -U SA -P 'Password12' -Q 'CREATE DATABASE test;' ``` ## Connect ```{r, eval = FALSE} con <- DBI::dbConnect(odbc::odbc(), driver = "ODBC Driver 17 for SQL Server", database = "test", uid = "SA", pwd = "Password12", server = "localhost", port = 1433 ) ``` dbplyr/vignettes/setup/_postgres.Rmd0000644000176200001440000000075314002647450017376 0ustar liggesusers# PostgreSQL ## Install First install PostgreSQL, create a data directory, and create a default database. ``` brew install postgresql export PGDATA=~/db/postgres-9.5 # set this globally somewhere initdb -E utf8 createdb createdb hadley createdb test createdb lahman createdb nycflights13 ``` ## Start ``` pg_ctl start ``` ## Connect ```{r, eval = FALSE} install.packages("RPostgreSQL") library(DBI) con <- dbConnect(RPostgreSQL::PostgreSQL(), dbname = "hadley") dbListTables(con) ``` dbplyr/R/0000755000176200001440000000000014031447261011751 5ustar liggesusersdbplyr/R/translate-sql-paste.R0000644000176200001440000000167213426147016016010 0ustar liggesusers#' @export #' @rdname sql_variant sql_paste <- function(default_sep, f = "CONCAT_WS") { function(..., sep = default_sep, collapse = NULL){ check_collapse(collapse) sql_call2(f, sep, ...) } } #' @export #' @rdname sql_variant sql_paste_infix <- function(default_sep, op, cast) { force(default_sep) op <- as.symbol(paste0("%", op, "%")) force(cast) function(..., sep = default_sep, collapse = NULL){ check_collapse(collapse) args <- list(...) if (length(args) == 1) { return(cast(args[[1]])) } if (sep == "") { infix <- function(x, y) sql_call2(op, x, y) } else { infix <- function(x, y) sql_call2(op, sql_call2(op, x, sep), y) } purrr::reduce(args, infix) } } check_collapse <- function(collapse) { if (is.null(collapse)) return() stop( "`collapse` not supported in DB translation of `paste()`.\n", "Please use str_flatten() instead", call. = FALSE ) } dbplyr/R/src_dbi.R0000644000176200001440000001077214004012136013475 0ustar liggesusers#' Use dplyr verbs with a remote database table #' #' All data manipulation on SQL tbls are lazy: they will not actually #' run the query or retrieve the data unless you ask for it: they all return #' a new `tbl_dbi` object. Use [compute()] to run the query and save the #' results in a temporary in the database, or use [collect()] to retrieve the #' results to R. You can see the query with [show_query()]. #' #' @details #' For best performance, the database should have an index on the variables #' that you are grouping by. Use [explain()] to check that the database is using #' the indexes that you expect. #' #' There is one verb that is not lazy: [do()] is eager because it must pull #' the data into R. #' #' @param src A `DBIConnection` object produced by `DBI::dbConnect()`. #' @param from Either a string (giving a table name), #' a fully qualified table name created by [in_schema()] #' or a literal [sql()] string. #' @param ... Passed on to [tbl_sql()] #' @export #' @examples #' library(dplyr) #' #' # Connect to a temporary in-memory SQLite database #' con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") #' #' # Add some data #' copy_to(con, mtcars) #' DBI::dbListTables(con) #' #' # To retrieve a single table from a source, use `tbl()` #' con %>% tbl("mtcars") #' #' # Use `in_schema()` for fully qualified table names #' con %>% tbl(in_schema("temp", "mtcars")) %>% head(1) #' #' # You can also use pass raw SQL if you want a more sophisticated query #' con %>% tbl(sql("SELECT * FROM mtcars WHERE cyl = 8")) #' #' # If you just want a temporary in-memory database, use src_memdb() #' src2 <- src_memdb() #' #' # To show off the full features of dplyr's database integration, #' # we'll use the Lahman database. lahman_sqlite() takes care of #' # creating the database. #' #' if (requireNamespace("Lahman", quietly = TRUE)) { #' batting <- copy_to(con, Lahman::Batting) #' batting #' #' # Basic data manipulation verbs work in the same way as with a tibble #' batting %>% filter(yearID > 2005, G > 130) #' batting %>% select(playerID:lgID) #' batting %>% arrange(playerID, desc(yearID)) #' batting %>% summarise(G = mean(G), n = n()) #' #' # There are a few exceptions. For example, databases give integer results #' # when dividing one integer by another. Multiply by 1 to fix the problem #' batting %>% #' select(playerID:lgID, AB, R, G) %>% #' mutate( #' R_per_game1 = R / G, #' R_per_game2 = R * 1.0 / G #' ) #' #' # All operations are lazy: they don't do anything until you request the #' # data, either by `print()`ing it (which shows the first ten rows), #' # or by `collect()`ing the results locally. #' system.time(recent <- filter(batting, yearID > 2010)) #' system.time(collect(recent)) #' #' # You can see the query that dplyr creates with show_query() #' batting %>% #' filter(G > 0) %>% #' group_by(playerID) %>% #' summarise(n = n()) %>% #' show_query() #' } #' @importFrom dplyr tbl #' @aliases tbl_dbi tbl.src_dbi <- function(src, from, ...) { subclass <- class(src$con)[[1]] # prefix added by dplyr::make_tbl tbl_sql(c(subclass, "dbi"), src = src, from = from, ...) } #' Database src #' #' @description #' `r lifecycle::badge("superseded")` #' #' Since can generate a `tbl()` directly from a DBI connection we no longer #' recommend using `src_dbi()`. #' #' @param con An object that inherits from [DBI::DBIConnection-class], #' typically generated by [DBI::dbConnect] #' @param auto_disconnect Should the connection be automatically closed when #' the src is deleted? Set to `TRUE` if you initialize the connection #' the call to `src_dbi()`. Pass `NA` to auto-disconnect but print a message #' when this happens. #' @return An S3 object with class `src_dbi`, `src_sql`, `src`. #' @keywords internal #' @export src_dbi <- function(con, auto_disconnect = FALSE) { # Avoid registering disconnector if con can't be evaluated force(con) # stopifnot(is(con, "DBIConnection")) if (is_false(auto_disconnect)) { disco <- NULL } else { disco <- db_disconnector(con, quiet = is_true(auto_disconnect)) } subclass <- paste0("src_", class(con)[[1]]) structure( list( con = con, disco = disco ), class = c(subclass, "src_dbi", "src_sql", "src") ) } setOldClass(c("src_dbi", "src_sql", "src")) # Creates an environment that disconnects the database when it's GC'd db_disconnector <- function(con, quiet = FALSE) { reg.finalizer(environment(), function(...) { if (!quiet) { message("Auto-disconnecting ", class(con)[[1]]) } dbDisconnect(con) }) environment() } dbplyr/R/backend-impala.R0000644000176200001440000000316614015731174014733 0ustar liggesusers#' Backend: Impala #' #' @description #' See `vignette("translate-function")` and `vignette("translate-verb")` for #' details of overall translation technology. Key differences for this backend #' are a scattering of custom translations provided by users, mostly focussed #' on bitwise operations. #' #' Use `simulate_impala()` with `lazy_frame()` to see simulated SQL without #' converting to live access database. #' #' @name backend-impala #' @aliases NULL #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_impala()) #' lf %>% transmute(X = bitwNot(bitwOr(b, c))) NULL #' @export #' @rdname simulate_dbi simulate_impala <- function() simulate_dbi("Impala") #' @export dbplyr_edition.Impala <- function(con) { 2L } #' @export sql_translation.Impala <- function(con) { sql_variant( scalar = sql_translator(.parent = base_odbc_scalar, bitwNot = sql_prefix("BITNOT", 1), bitwAnd = sql_prefix("BITAND", 2), bitwOr = sql_prefix("BITOR", 2), bitwXor = sql_prefix("BITXOR", 2), bitwShiftL = sql_prefix("SHIFTLEFT", 2), bitwShiftR = sql_prefix("SHIFTRIGHT", 2), as.Date = sql_cast("VARCHAR(10)"), ceiling = sql_prefix("CEIL") ) , base_odbc_agg, base_odbc_win ) } #' @export sql_table_analyze.Impala <- function(con, table, ...) { # Using COMPUTE STATS instead of ANALYZE as recommended in this article # https://www.cloudera.com/documentation/enterprise/5-9-x/topics/impala_compute_stats.html build_sql("COMPUTE STATS ", as.sql(table, con = con), con = con) } dbplyr/R/backend-teradata.R0000644000176200001440000000723314015731174015254 0ustar liggesusers#' Backend: Teradata #' #' @description #' See `vignette("translate-function")` and `vignette("translate-verb")` for #' details of overall translation technology. Key differences for this backend #' are: #' #' * Uses `TOP` instead of `LIMIT` #' * Selection of user supplied translations #' #' Use `simulate_teradata()` with `lazy_frame()` to see simulated SQL without #' converting to live access database. #' #' @name backend-teradata #' @aliases NULL #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_teradata()) #' lf %>% head() NULL #' @export #' @rdname backend-teradata simulate_teradata <- function() simulate_dbi("Teradata") #' @export dbplyr_edition.Teradata <- function(con) { 2L } #' @export sql_query_select.Teradata <- function(con, select, from, where = NULL, group_by = NULL, having = NULL, order_by = NULL, limit = NULL, distinct = FALSE, ..., subquery = FALSE) { sql_select_clauses(con, select = sql_clause_select(con, select, distinct, top = limit), from = sql_clause_from(con, from), where = sql_clause_where(con, where), group_by = sql_clause_group_by(con, group_by), having = sql_clause_having(con, having), order_by = sql_clause_order_by(con, order_by, subquery, limit) ) } #' @export sql_translation.Teradata <- function(con) { sql_variant( sql_translator(.parent = base_odbc_scalar, `!=` = sql_infix("<>"), bitwNot = sql_prefix("BITNOT", 1), bitwAnd = sql_prefix("BITAND", 2), bitwOr = sql_prefix("BITOR", 2), bitwXor = sql_prefix("BITXOR", 2), bitwShiftL = sql_prefix("SHIFTLEFT", 2), bitwShiftR = sql_prefix("SHIFTRIGHT", 2), as.numeric = sql_cast("NUMERIC"), as.double = sql_cast("NUMERIC"), as.character = sql_cast("VARCHAR(MAX)"), log10 = sql_prefix("LOG"), log = sql_log(), cot = sql_cot(), quantile = sql_quantile("APPROX_PERCENTILE"), median = sql_median("APPROX_PERCENTILE"), nchar = sql_prefix("CHARACTER_LENGTH"), ceil = sql_prefix("CEILING"), ceiling = sql_prefix("CEILING"), atan2 = function(x, y) { sql_expr(ATAN2(!!y, !!x)) }, substr = function(x, start, stop) { len <- stop - start + 1 sql_expr(SUBSTR(!!x, !!start, !!len)) }, paste = function(...) { stop( "`paste()`` is not supported in this SQL variant, try `paste0()` instead", call. = FALSE ) } ), sql_translator(.parent = base_odbc_agg, cor = sql_not_supported("cor()"), cov = sql_not_supported("cov()"), var = sql_aggregate("VAR_SAMP", "var"), ), sql_translator(.parent = base_odbc_win, cor = win_absent("cor"), cov = win_absent("cov"), var = win_recycled("VAR_SAMP") ) )} #' @export sql_table_analyze.Teradata <- function(con, table, ...) { # https://www.tutorialspoint.com/teradata/teradata_statistics.htm build_sql("COLLECT STATISTICS ", as.sql(table, con = con) , con = con) } utils::globalVariables(c("ATAN2", "SUBSTR")) dbplyr/R/utils-format.R0000644000176200001440000000120613426410503014515 0ustar liggesuserswrap <- function(..., indent = 0) { x <- paste0(..., collapse = "") wrapped <- strwrap( x, indent = indent, exdent = indent + 2, width = getOption("width") ) paste0(wrapped, collapse = "\n") } indent <- function(x) { x <- paste0(x, collapse = "\n") paste0(" ", gsub("\n", "\n ", x)) } indent_print <- function(x) { indent(utils::capture.output(print(x))) } # function for the thousand separator, # returns "," unless it's used for the decimal point, in which case returns "." 'big_mark' <- function(x, ...) { mark <- if (identical(getOption("OutDec"), ",")) "." else "," formatC(x, big.mark = mark, ...) } dbplyr/R/backend-access.R0000644000176200001440000001352414031447261014727 0ustar liggesusers#' Backend: MS Access #' #' @description #' See `vignette("translate-function")` and `vignette("translate-verb")` for #' details of overall translation technology. Key differences for this backend #' are: #' #' * `SELECT` uses `TOP`, not `LIMIT` #' * Non-standard types and mathematical functions #' * String concatenation uses `&` #' * No `ANALYZE` equivalent #' * `TRUE` and `FALSE` converted to 1 and 0 #' #' Use `simulate_access()` with `lazy_frame()` to see simulated SQL without #' converting to live access database. #' #' @name backend-access #' @aliases NULL #' @examples #' library(dplyr, warn.conflicts = FALSE) #' lf <- lazy_frame(x = 1, y = 2, z = "a", con = simulate_access()) #' #' lf %>% head() #' lf %>% mutate(y = as.numeric(y), z = sqrt(x^2 + 10)) #' lf %>% mutate(a = paste0(z, " times")) NULL #' @export #' @rdname backend-access simulate_access <- function() simulate_dbi("ACCESS") #' @export dbplyr_edition.ACCESS <- function(con) { 2L } # sql_ generics -------------------------------------------- #' @export sql_query_select.ACCESS <- function(con, select, from, where = NULL, group_by = NULL, having = NULL, order_by = NULL, limit = NULL, distinct = FALSE, ..., subquery = FALSE) { sql_select_clauses(con, select = sql_clause_select(con, select, distinct, top = limit), from = sql_clause_from(con, from), where = sql_clause_where(con, where), group_by = sql_clause_group_by(con, group_by), having = sql_clause_having(con, having), order_by = sql_clause_order_by(con, order_by, subquery, limit) ) } #' @export sql_translation.ACCESS <- function(con) { sql_variant( sql_translator(.parent = base_scalar, # Much of this translation comes from: https://www.techonthenet.com/access/functions/ # Conversion as.numeric = sql_prefix("CDBL"), as.double = sql_prefix("CDBL"), # as.integer() always rounds down. CInt does not, but Int does as.integer = sql_prefix("INT"), as.logical = sql_prefix("CBOOL"), as.character = sql_prefix("CSTR"), as.Date = sql_prefix("CDATE"), # Math exp = sql_prefix("EXP"), log = sql_prefix("LOG"), log10 = function(x) { sql_expr(log(!!x) / log(10L)) }, sqrt = sql_prefix("SQR"), sign = sql_prefix("SGN"), floor = sql_prefix("INT"), # Nearly add 1, then drop off the decimal. This results in the equivalent to ceiling() ceiling = function(x) { sql_expr(int(!!x + .9999999999)) }, ceil = function(x) { sql_expr(int(!!x + .9999999999)) }, # There is no POWER function in Access. It uses ^ instead `^` = function(x, y) { sql_expr((!!x) ^ (!!y)) }, # Strings nchar = sql_prefix("LEN"), tolower = sql_prefix("LCASE"), toupper = sql_prefix("UCASE"), # Pull `left` chars from the left, then `right` chars from the right to replicate substr substr = function(x, start, stop){ right <- stop - start + 1 left <- stop sql_expr(right(left(!!x, !!left), !!right)) }, trimws = sql_prefix("TRIM"), # No support for CONCAT in Access paste = sql_paste_infix(" ", "&", function(x) sql_expr(CStr(!!x))), paste0 = sql_paste_infix("", "&", function(x) sql_expr(CStr(!!x))), # Logic # Access always returns -1 for True and 0 for False is.null = sql_prefix("ISNULL"), is.na = sql_prefix("ISNULL"), # IIF() is like ifelse() ifelse = function(test, yes, no){ sql_expr(iif(!!test, !!yes, !!no)) }, # Coalesce doesn't exist in Access. # NZ() only works while in Access, not with the Access driver # IIF(ISNULL()) is the best way to construct this coalesce = function(x, y) { sql_expr(iif(isnull(!!x), !!y, !!x)) }, # pmin/pmax for 2 columns pmin = function(x, y) { sql_expr(iif(!!x <= !!y, !!x, !!y)) }, pmax = function(x, y) { sql_expr(iif(!!x <= !!y, !!y, !!x)) }, # Dates Sys.Date = sql_prefix("DATE") ), sql_translator(.parent = base_agg, sd = sql_aggregate("STDEV"), var = sql_aggregate("VAR"), cor = sql_not_supported("cor"), cov = sql_not_supported("cov"), # Count(Distinct *) does not work in Access # This would work but we don't know the table name when translating: # SELECT Count(*) FROM (SELECT DISTINCT * FROM table_name) AS T n_distinct = sql_not_supported("n_distinct"), ), # Window functions not supported in Access sql_translator(.parent = base_no_win) ) } # db_ generics ----------------------------------- #' @export sql_table_analyze.ACCESS <- function(con, table, ...) { # Do nothing. Access doesn't support an analyze / update statistics function NULL } # Util ------------------------------------------- #' @export sql_escape_logical.ACCESS <- function(con, x) { # Access uses a convention of -1 as True and 0 as False y <- ifelse(x, -1, 0) y[is.na(x)] <- "NULL" y } #' @export sql_escape_date.ACCESS <- function(con, x) { # Access delimits dates using octothorpes, and uses YYYY-MM-DD y <- format(x, "#%Y-%m-%d#") y[is.na(x)] <- "NULL" y } #' @export sql_escape_datetime.ACCESS <- function(con, x) { # Access delimits datetimes using octothorpes, and uses YYYY-MM-DD HH:MM:SS # Timezones are not supported in Access y <- format(x, "#%Y-%m-%d %H:%M:%S#") y[is.na(x)] <- "NULL" y } globalVariables(c("CStr", "iif", "isnull", "text")) dbplyr/R/verb-set-ops.R0000644000176200001440000000464514002647450014433 0ustar liggesusers#' SQL set operations #' #' These are methods for the dplyr generics `dplyr::intersect()`, #' `dplyr::union()`, and `dplyr::setdiff()`. They are translated to #' `INTERSECT`, `UNION`, and `EXCEPT` respectively. #' #' @inheritParams left_join.tbl_lazy #' @param ... Not currently used; provided for future extensions. #' @param all If `TRUE`, includes all matches in output, not just unique rows. # registered onLoad #' @importFrom dplyr intersect intersect.tbl_lazy <- function(x, y, copy = FALSE, ..., all = FALSE) { add_op_set_op(x, y, "INTERSECT", copy = copy, ..., all = all) } # registered onLoad #' @importFrom dplyr union #' @rdname intersect.tbl_lazy union.tbl_lazy <- function(x, y, copy = FALSE, ..., all = FALSE) { add_op_set_op(x, y, "UNION", copy = copy, ..., all = all) } #' @export #' @importFrom dplyr union_all #' @rdname intersect.tbl_lazy union_all.tbl_lazy <- function(x, y, copy = FALSE, ...) { add_op_set_op(x, y, "UNION ALL", copy = copy, ..., all = FALSE) } # registered onLoad #' @importFrom dplyr setdiff #' @rdname intersect.tbl_lazy setdiff.tbl_lazy <- function(x, y, copy = FALSE, ..., all = FALSE) { add_op_set_op(x, y, "EXCEPT", copy = copy, ..., all = all) } add_op_set_op <- function(x, y, type, copy = FALSE, ..., all = FALSE) { y <- auto_copy(x, y, copy) if (inherits(x$src$con, "SQLiteConnection")) { # LIMIT only part the compound-select-statement not the select-core. # # https://www.sqlite.org/syntax/compound-select-stmt.html # https://www.sqlite.org/syntax/select-core.html if (inherits(x$ops, "op_head") || inherits(y$ops, "op_head")) { stop("SQLite does not support set operations on LIMITs", call. = FALSE) } } # Ensure each has same variables vars <- union(op_vars(x), op_vars(y)) x <- fill_vars(x, vars) y <- fill_vars(y, vars) x$ops <- op_double("set_op", x, y, args = list(type = type, all = all)) x } fill_vars <- function(x, vars) { x_vars <- op_vars(x) if (identical(x_vars, vars)) { return(x) } new_vars <- lapply(set_names(vars), function(var) { if (var %in% x_vars) { sym(var) } else { NA } }) x$ops <- op_select(x$ops, new_vars) x } #' @export op_vars.op_set_op <- function(op) { union(op_vars(op$x), op_vars(op$y)) } #' @export sql_build.op_set_op <- function(op, con, ...) { # add_op_set_op() ensures that both have same variables set_op_query(op$x, op$y, type = op$args$type, all = op$args$all) } dbplyr/R/verb-joins.R0000644000176200001440000002214214002647450014153 0ustar liggesusers#' Join SQL tables #' #' @description #' These are methods for the dplyr [join] generics. They are translated #' to the following SQL queries: #' #' * `inner_join(x, y)`: `SELECT * FROM x JOIN y ON x.a = y.a` #' * `left_join(x, y)`: `SELECT * FROM x LEFT JOIN y ON x.a = y.a` #' * `right_join(x, y)`: `SELECT * FROM x RIGHT JOIN y ON x.a = y.a` #' * `full_join(x, y)`: `SELECT * FROM x FULL JOIN y ON x.a = y.a` #' * `semi_join(x, y)`: `SELECT * FROM x WHERE EXISTS (SELECT 1 FROM y WHERE x.a = y.a)` #' * `anti_join(x, y)`: `SELECT * FROM x WHERE NOT EXISTS (SELECT 1 FROM y WHERE x.a = y.a)` #' #' @param x,y A pair of lazy data frames backed by database queries. #' @inheritParams dplyr::join #' @param copy If `x` and `y` are not from the same data source, #' and `copy` is `TRUE`, then `y` will be copied into a #' temporary table in same database as `x`. `*_join()` will automatically #' run `ANALYZE` on the created table in the hope that this will make #' you queries as efficient as possible by giving more data to the query #' planner. #' #' This allows you to join tables across srcs, but it's potentially expensive #' operation so you must opt into it. #' @param auto_index if `copy` is `TRUE`, automatically create #' indices for the variables in `by`. This may speed up the join if #' there are matching indexes in `x`. #' @param sql_on A custom join predicate as an SQL expression. #' Usually joins use column equality, but you can perform more complex #' queries by supply `sql_on` which should be a SQL expression that #' uses `LHS` and `RHS` aliases to refer to the left-hand side or #' right-hand side of the join respectively. #' @param na_matches Should NA (NULL) values match one another? #' The default, "never", is how databases usually work. `"na"` makes #' the joins behave like the dplyr join functions, [merge()], [match()], #' and `%in%`. #' @inherit arrange.tbl_lazy return #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' band_db <- tbl_memdb(dplyr::band_members) #' instrument_db <- tbl_memdb(dplyr::band_instruments) #' band_db %>% left_join(instrument_db) %>% show_query() #' #' # Can join with local data frames by setting copy = TRUE #' band_db %>% #' left_join(dplyr::band_instruments, copy = TRUE) #' #' # Unlike R, joins in SQL don't usually match NAs (NULLs) #' db <- memdb_frame(x = c(1, 2, NA)) #' label <- memdb_frame(x = c(1, NA), label = c("one", "missing")) #' db %>% left_join(label, by = "x") #' # But you can activate R's usual behaviour with the na_matches argument #' db %>% left_join(label, by = "x", na_matches = "na") #' #' # By default, joins are equijoins, but you can use `sql_on` to #' # express richer relationships #' db1 <- memdb_frame(x = 1:5) #' db2 <- memdb_frame(x = 1:3, y = letters[1:3]) #' db1 %>% left_join(db2) %>% show_query() #' db1 %>% left_join(db2, sql_on = "LHS.x < RHS.x") %>% show_query() #' @name join.tbl_sql NULL #' @rdname join.tbl_sql #' @export #' @importFrom dplyr inner_join inner_join.tbl_lazy <- function(x, y, by = NULL, copy = FALSE, suffix = NULL, auto_index = FALSE, ..., sql_on = NULL, na_matches = c("never", "na")) { add_op_join( x, y, "inner", by = by, sql_on = sql_on, copy = copy, suffix = suffix, auto_index = auto_index, na_matches = na_matches, ... ) } #' @rdname join.tbl_sql #' @export #' @importFrom dplyr left_join left_join.tbl_lazy <- function(x, y, by = NULL, copy = FALSE, suffix = NULL, auto_index = FALSE, ..., sql_on = NULL, na_matches = c("never", "na")) { add_op_join( x, y, "left", by = by, sql_on = sql_on, copy = copy, suffix = suffix, auto_index = auto_index, na_matches = na_matches, ... ) } #' @rdname join.tbl_sql #' @export #' @importFrom dplyr right_join right_join.tbl_lazy <- function(x, y, by = NULL, copy = FALSE, suffix = NULL, auto_index = FALSE, ..., sql_on = NULL, na_matches = c("never", "na")) { add_op_join( x, y, "right", by = by, sql_on = sql_on, copy = copy, suffix = suffix, auto_index = auto_index, na_matches = na_matches, ... ) } #' @rdname join.tbl_sql #' @export #' @importFrom dplyr full_join full_join.tbl_lazy <- function(x, y, by = NULL, copy = FALSE, suffix = NULL, auto_index = FALSE, ..., sql_on = NULL, na_matches = c("never", "na")) { add_op_join( x, y, "full", by = by, sql_on = sql_on, copy = copy, suffix = suffix, auto_index = auto_index, na_matches = na_matches, ... ) } #' @rdname join.tbl_sql #' @export #' @importFrom dplyr semi_join semi_join.tbl_lazy <- function(x, y, by = NULL, copy = FALSE, auto_index = FALSE, ..., sql_on = NULL, na_matches = c("never", "na")) { add_op_semi_join( x, y, anti = FALSE, by = by, sql_on = sql_on, copy = copy, auto_index = auto_index, na_matches = na_matches, ... ) } #' @rdname join.tbl_sql #' @export #' @importFrom dplyr anti_join anti_join.tbl_lazy <- function(x, y, by = NULL, copy = FALSE, auto_index = FALSE, ..., sql_on = NULL, na_matches = c("never", "na")) { add_op_semi_join( x, y, anti = TRUE, by = by, sql_on = sql_on, copy = copy, auto_index = auto_index, na_matches = na_matches, ... ) } add_op_join <- function(x, y, type, by = NULL, sql_on = NULL, copy = FALSE, suffix = NULL, auto_index = FALSE, na_matches = "never") { if (!is.null(sql_on)) { by <- list(x = character(0), y = character(0), on = sql(sql_on)) } else if (identical(type, "full") && identical(by, character())) { type <- "cross" by <- list(x = character(0), y = character(0)) } else { by <- dplyr::common_by(by, x, y) } y <- auto_copy( x, y, copy = copy, indexes = if (auto_index) list(by$y) ) suffix <- suffix %||% sql_join_suffix(x$src$con, suffix) vars <- join_vars(op_vars(x), op_vars(y), type = type, by = by, suffix = suffix) x$ops <- op_double("join", x, y, args = list( vars = vars, type = type, by = by, suffix = suffix, na_matches = na_matches )) x } add_op_semi_join <- function(x, y, anti = FALSE, by = NULL, sql_on = NULL, copy = FALSE, auto_index = FALSE, na_matches = "never") { if (!is.null(sql_on)) { by <- list(x = character(0), y = character(0), on = sql(sql_on)) } else { by <- dplyr::common_by(by, x, y) } y <- auto_copy( x, y, copy, indexes = if (auto_index) list(by$y) ) x$ops <- op_double("semi_join", x, y, args = list( anti = anti, by = by, na_matches = na_matches )) x } join_vars <- function(x_names, y_names, type, by, suffix = c(".x", ".y")) { # Remove join keys from y y_names <- setdiff(y_names, by$y) # Add suffix where needed suffix <- check_suffix(suffix) x_new <- add_suffixes(x_names, y_names, suffix$x) y_new <- add_suffixes(y_names, x_names, suffix$y) # In left and inner joins, return key values only from x # In right joins, return key values only from y # In full joins, return key values by coalescing values from x and y x_x <- x_names x_y <- by$y[match(x_names, by$x)] x_y[type == "left" | type == "inner"] <- NA x_x[type == "right" & !is.na(x_y)] <- NA y_x <- rep_len(NA, length(y_names)) y_y <- y_names # Return a list with 3 parallel vectors # At each position, values in the 3 vectors represent # alias - name of column in join result # x - name of column from left table or NA if only from right table # y - name of column from right table or NA if only from left table list( alias = c(x_new, y_new), x = c(x_x, y_x), y = c(x_y, y_y), all_x = x_names, all_y = c(y_names, by$y) ) } check_suffix <- function(x) { if (!is.character(x) || length(x) != 2) { stop("`suffix` must be a character vector of length 2.", call. = FALSE) } list(x = x[1], y = x[2]) } add_suffixes <- function(x, y, suffix) { if (identical(suffix, "")) { return(x) } out <- character(length(x)) for (i in seq_along(x)) { nm <- x[[i]] while (nm %in% y || nm %in% out) { nm <- paste0(nm, suffix) } out[[i]] <- nm } out } #' @export op_vars.op_join <- function(op) { op$args$vars$alias } #' @export op_vars.op_semi_join <- function(op) { op_vars(op$x) } #' @export sql_build.op_join <- function(op, con, ...) { join_query( op$x, op$y, vars = op$args$vars, type = op$args$type, by = op$args$by, suffix = op$args$suffix, na_matches = op$args$na_matches ) } #' @export sql_build.op_semi_join <- function(op, con, ...) { semi_join_query( op$x, op$y, anti = op$args$anti, by = op$args$by, na_matches = op$args$na_matches ) } dbplyr/R/verb-do-query.R0000644000176200001440000000170514002647450014600 0ustar liggesusers#' @importFrom R6 R6Class NULL Query <- R6::R6Class("Query", private = list( .vars = NULL ), public = list( con = NULL, sql = NULL, initialize = function(con, sql, vars) { self$con <- con self$sql <- sql private$.vars <- vars }, print = function(...) { cat(" ", self$sql, "\n", sep = "") print(self$con) }, fetch = function(n = -1L) { res <- dbSendQuery(self$con, self$sql) on.exit(dbClearResult(res)) out <- dbFetch(res, n) res_warn_incomplete(res) out }, fetch_paged = function(chunk_size = 1e4, callback) { qry <- dbSendQuery(self$con, self$sql) on.exit(dbClearResult(qry)) while (!dbHasCompleted(qry)) { chunk <- dbFetch(qry, chunk_size) callback(chunk) } invisible(TRUE) }, vars = function() { private$.vars }, ncol = function() { length(self$vars()) } ) ) dbplyr/R/dbplyr.R0000644000176200001440000000050014002647450013363 0ustar liggesusers#' @importFrom assertthat assert_that #' @importFrom assertthat is.flag #' @importFrom stats setNames update #' @importFrom utils head tail #' @importFrom glue glue #' @importFrom dplyr n #' @import rlang #' @import DBI #' @importFrom tibble tibble as_tibble #' @importFrom magrittr %>% #' @keywords internal "_PACKAGE" dbplyr/R/utils.R0000644000176200001440000000452114002647450013236 0ustar liggesusersdeparse_trunc <- function(x, width = getOption("width")) { text <- deparse(x, width.cutoff = width) if (length(text) == 1 && nchar(text) < width) return(text) paste0(substr(text[1], 1, width - 3), "...") } is.wholenumber <- function(x) { trunc(x) == x } deparse_all <- function(x) { x <- purrr::map_if(x, is_formula, f_rhs) purrr::map_chr(x, expr_text, width = 500L) } #' Provides comma-separated string out of the parameters #' @export #' @keywords internal #' @param ... Arguments to be constructed into the string named_commas <- function(...) { x <- unlist(purrr::map(list2(...), as.character)) if (is_null(names(x))) { paste0(x, collapse = ", ") } else { paste0(names(x), " = ", x, collapse = ", ") } } commas <- function(...) paste0(..., collapse = ", ") in_travis <- function() identical(Sys.getenv("TRAVIS"), "true") unique_table_name <- function() { # Needs to use option to unique names across reloads while testing i <- getOption("dbplyr_table_name", 0) + 1 options(dbplyr_table_name = i) sprintf("dbplyr_%03i", i) } unique_subquery_name <- function() { # Needs to use option so can reset at the start of each query i <- getOption("dbplyr_subquery_name", 0) + 1 options(dbplyr_subquery_name = i) sprintf("q%02i", i) } unique_subquery_name_reset <- function() { options(dbplyr_subquery_name = 0) } succeeds <- function(x, quiet = FALSE) { tryCatch( { x TRUE }, error = function(e) { if (!quiet) message("Error: ", e$message) FALSE } ) } c_character <- function(...) { x <- c(...) if (length(x) == 0) { return(character()) } if (!is.character(x)) { stop("Character input expected", call. = FALSE) } x } cat_line <- function(...) cat(paste0(..., "\n"), sep = "") res_warn_incomplete <- function(res, hint = "n = -1") { if (dbHasCompleted(res)) return() rows <- big_mark(dbGetRowCount(res)) warning("Only first ", rows, " results retrieved. Use ", hint, " to retrieve all.", call. = FALSE) } hash_temp <- function(name) { name <- ident(paste0("#", name)) inform( paste0("Created a temporary table named ", name), class = c("dbplyr_message_temp_table", "dbplyr_message") ) name } # Helper for testing local_methods <- function(..., .frame = caller_env()) { local_bindings(..., .env = global_env(), .frame = .frame) } dbplyr/R/verb-select.R0000644000176200001440000001132414004012136014275 0ustar liggesusers#' Subset, rename, and reorder columns using their names #' #' @description #' These are methods for the dplyr [select()], [rename()], and [relocate()] #' generics. They generate the `SELECT` clause of the SQL query. #' #' These functions do not support predicate functions, i.e. you can #' not use `where(is.numeric)` to select all numeric variables. #' #' @inheritParams arrange.tbl_lazy #' @inheritParams dplyr::select #' @export #' @importFrom dplyr select #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' db <- memdb_frame(x = 1, y = 2, z = 3) #' db %>% select(-y) %>% show_query() #' db %>% relocate(z) %>% show_query() #' db %>% rename(first = x, last = z) %>% show_query() select.tbl_lazy <- function(.data, ...) { sim_data <- simulate_vars(.data) sim_data <- group_by(sim_data, !!!syms(group_vars(.data))) loc <- tidyselect::eval_select(expr(c(...)), sim_data) loc <- ensure_group_vars(loc, sim_data, notify = TRUE) new_vars <- set_names(names(sim_data)[loc], names(loc)) .data$ops <- op_select(.data$ops, syms(new_vars)) .data } ensure_group_vars <- function(loc, data, notify = TRUE) { group_loc <- match(group_vars(data), colnames(data)) missing <- setdiff(group_loc, loc) if (length(missing) > 0) { vars <- names(data)[missing] if (notify) { inform(glue( "Adding missing grouping variables: ", paste0("`", names(data)[missing], "`", collapse = ", ") )) } loc <- c(set_names(missing, vars), loc) } loc } #' @rdname select.tbl_lazy #' @importFrom dplyr rename #' @export rename.tbl_lazy <- function(.data, ...) { sim_data <- simulate_vars(.data) loc <- tidyselect::eval_rename(expr(c(...)), sim_data) new_vars <- set_names(names(sim_data), names(sim_data)) names(new_vars)[loc] <- names(loc) .data$ops <- op_select(.data$ops, syms(new_vars)) .data } #' @rdname select.tbl_lazy #' @importFrom dplyr rename_with #' @importFrom tidyselect everything #' @inheritParams dplyr::rename_with #' @export rename_with.tbl_lazy <- function(.data, .fn, .cols = everything(), ...) { .fn <- as_function(.fn) cols <- tidyselect::eval_select(enquo(.cols), simulate_vars(.data)) new_vars <- set_names(op_vars(.data)) names(new_vars)[cols] <- .fn(new_vars[cols], ...) .data$ops <- op_select(.data$ops, syms(new_vars)) .data } #' @rdname select.tbl_lazy #' @importFrom dplyr relocate #' @inheritParams dplyr::relocate #' @export relocate.tbl_lazy <- function(.data, ..., .before = NULL, .after = NULL) { vars <- simulate_vars(.data) new_vars <- dplyr::relocate( simulate_vars(.data), ..., .before = {{.before}}, .after = {{.after}} ) .data$ops <- op_select(.data$ops, syms(set_names(names(new_vars)))) .data } simulate_vars <- function(x) { as_tibble(rep_named(op_vars(x), list(logical()))) } # op_select --------------------------------------------------------------- op_select <- function(x, vars) { if (inherits(x, "op_select")) { # Special optimisation when applied to pure projection() - this is # conservative and we could expand to any op_select() if combined with # the logic in nest_vars() prev_vars <- x$args$vars if (purrr::every(vars, is.symbol)) { # if current operation is pure projection # we can just subset the previous selection sel_vars <- purrr::map_chr(vars, as_string) vars <- set_names(prev_vars[sel_vars], names(sel_vars)) x <- x$x } else if (purrr::every(prev_vars, is.symbol)) { # if previous operation is pure projection sel_vars <- purrr::map_chr(prev_vars, as_string) if (all(names(sel_vars) == sel_vars)) { # and there's no renaming # we can just ignore the previous step x <- x$x } } } new_op_select(x, vars) } # SELECT in the SQL sense - powers select(), rename(), mutate(), and transmute() new_op_select <- function(x, vars) { stopifnot(inherits(x, "op")) stopifnot(is.list(vars)) op_single("select", x, dots = list(), args = list(vars = vars)) } #' @export op_vars.op_select <- function(op) { names(op$args$vars) } #' @export op_grps.op_select <- function(op) { # Find renamed variables symbols <- purrr::keep(op$args$vars, is_symbol) new2old <- purrr::map_chr(symbols, as_string) old2new <- set_names(names(new2old), new2old) grps <- op_grps(op$x) renamed <- grps %in% names(old2new) grps[renamed] <- old2new[grps[renamed]] grps } #' @export sql_build.op_select <- function(op, con, ...) { new_vars <- translate_sql_( op$args$vars, con, vars_group = op_grps(op), vars_order = translate_sql_(op_sort(op), con, context = list(clause = "ORDER")), vars_frame = op_frame(op), context = list(clause = "SELECT") ) select_query( sql_build(op$x, con), select = new_vars ) } dbplyr/R/verb-mutate.R0000644000176200001440000000472014002647450014332 0ustar liggesusers#' Create, modify, and delete columns #' #' These are methods for the dplyr [mutate()] and [transmute()] generics. #' They are translated to computed expressions in the `SELECT` clause of #' the SQL query. #' #' @inheritParams arrange.tbl_lazy #' @inheritParams dplyr::mutate #' @inherit arrange.tbl_lazy return #' @export #' @importFrom dplyr mutate #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' db <- memdb_frame(x = 1:5, y = 5:1) #' db %>% #' mutate(a = (x + y) / 2, b = sqrt(x^2L + y^2L)) %>% #' show_query() #' #' # dbplyr automatically creates subqueries as needed #' db %>% #' mutate(x1 = x + 1, x2 = x1 * 2) %>% #' show_query() mutate.tbl_lazy <- function(.data, ...) { dots <- quos(..., .named = TRUE) dots <- partial_eval_dots(dots, vars = op_vars(.data)) nest_vars(.data, dots, union(op_vars(.data), op_grps(.data))) } #' @export #' @importFrom dplyr transmute transmute.tbl_lazy <- function(.data, ...) { dots <- quos(..., .named = TRUE) dots <- partial_eval_dots(dots, vars = op_vars(.data)) nest_vars(.data, dots, character()) } # helpers ----------------------------------------------------------------- # TODO: refactor to remove `.data` argument and return a list of layers. nest_vars <- function(.data, dots, all_vars) { # For each expression, check if it uses any newly created variables. # If so, nest the mutate() new_vars <- character() init <- 0L for (i in seq_along(dots)) { cur_var <- names(dots)[[i]] used_vars <- all_names(get_expr(dots[[i]])) if (any(used_vars %in% new_vars)) { new_actions <- dots[seq2(init, length(dots))][new_vars] .data$ops <- op_select(.data$ops, carry_over(union(all_vars, used_vars), new_actions)) all_vars <- c(all_vars, setdiff(new_vars, all_vars)) new_vars <- cur_var init <- i } else { new_vars <- c(new_vars, cur_var) } } if (init != 0L) { dots <- dots[-seq2(1L, init - 1)] } .data$ops <- op_select(.data$ops, carry_over(all_vars, dots)) .data } # Combine a selection (passed through from subquery) # with new actions carry_over <- function(sel = character(), act = list()) { if (is.null(names(sel))) { names(sel) <- sel } sel <- syms(sel) # Keep last of duplicated acts act <- act[!duplicated(names(act), fromLast = TRUE)] # Preserve order of sel both <- intersect(names(sel), names(act)) sel[both] <- act[both] # Adding new variables at end new <- setdiff(names(act), names(sel)) c(sel, act[new]) } dbplyr/R/backend-hana.R0000644000176200001440000000423114006567571014401 0ustar liggesusers#' Backend: SAP HANA #' #' @description #' See `vignette("translate-function")` and `vignette("translate-verb")` for #' details of overall translation technology. Key differences for this backend #' are: #' #' * Temporary tables get `#` prefix and use `LOCAL TEMPORARY COLUMN`. #' * No table analysis performed in [copy_to()]. #' * `paste()` uses `||` #' * Note that you can't create new boolean columns from logical expressions; #' you need to wrap with explicit `ifelse`: `ifelse(x > y, TRUE, FALSE)`. #' #' Use `simulate_hana()` with `lazy_frame()` to see simulated SQL without #' converting to live access database. #' #' @name backend-hana #' @aliases NULL #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_hana()) #' lf %>% transmute(x = paste0(z, " times")) NULL #' @export #' @rdname backend-hana simulate_hana <- function() simulate_dbi("HDB") #' @export dbplyr_edition.HDB <- function(con) { 2L } #' @export sql_translation.HDB <- function(con) { sql_variant( sql_translator(.parent = base_scalar, as.character = sql_cast("SHORTTEXT"), as.numeric = sql_cast("DOUBLE"), as.double = sql_cast("DOUBLE"), # string functions ------------------------------------------------ paste = sql_paste_infix(" ", "||", function(x) sql_expr(cast(!!x %as% text))), paste0 = sql_paste_infix("", "||", function(x) sql_expr(cast(!!x %as% text))), str_c = sql_paste_infix("", "||", function(x) sql_expr(cast(!!x %as% text))), # https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/20e8341275191014a4cfdcd3c830fc98.html substr = sql_substr("SUBSTRING"), substring = sql_substr("SUBSTRING"), str_sub = sql_str_sub("SUBSTRING"), ), base_agg, base_win ) } #' @export db_table_temporary.HDB <- function(con, table, temporary) { if (temporary && substr(table, 1, 1) != "#") { table <- hash_temp(table) } list( table = table, temporary = FALSE ) } #' @export `sql_table_analyze.HDB` <- function(con, table, ...) { # CREATE STATISTICS doesn't work for temporary tables, so # don't do anything at all } dbplyr/R/src-sql.R0000644000176200001440000000172114002647450013461 0ustar liggesusers#' Create a "sql src" object #' #' Deprecated: please use directly use a `DBIConnection` object instead. #' #' @keywords internal #' @export #' @param subclass name of subclass. "src_sql" is an abstract base class, so you #' must supply this value. `src_` is automatically prepended to the #' class name #' @param con the connection object #' @param ... fields used by object src_sql <- function(subclass, con, ...) { subclass <- paste0("src_", subclass) structure(list(con = con, ...), class = c(subclass, "src_sql", "src")) } #' @importFrom dplyr same_src #' @export same_src.src_sql <- function(x, y) { if (!inherits(y, "src_sql")) return(FALSE) identical(x$con, y$con) } #' @importFrom dplyr src_tbls #' @export src_tbls.src_sql <- function(x, ...) { dbListTables(x$con) } #' @export format.src_sql <- function(x, ...) { paste0( "src: ", dbplyr_connection_describe(x$con), "\n", wrap("tbls: ", paste0(sort(src_tbls(x)), collapse = ", ")) ) } dbplyr/R/translate-sql-quantile.R0000644000176200001440000000211014002647450016500 0ustar liggesuserssql_quantile <- function(f, style = c("infix", "ordered"), window = FALSE) { force(f) style <- match.arg(style) force(window) function(x, probs) { check_probs(probs) sql <- switch(style, infix = sql_call2(f, x, probs), ordered = build_sql( sql_call2(f, probs), " WITHIN GROUP (ORDER BY ", x, ")" ) ) if (window) { sql <- win_over(sql, partition = win_current_group(), frame = win_current_frame() ) } sql } } sql_median <- function(f, style = c("infix", "ordered"), window = FALSE) { warned <- FALSE quantile <- sql_quantile(f, style = style, window = window) function(x, na.rm = FALSE) { warned <<- check_na_rm("median", na.rm, warned) quantile(x, 0.5) } } check_probs <- function(probs) { if (!is.numeric(probs)) { stop("`probs` must be numeric", call. = FALSE) } if (length(probs) > 1) { stop("SQL translation only supports single value for `probs`.", call. = FALSE) } } dbplyr/R/zzz.R0000644000176200001440000000407414006531012012724 0ustar liggesusers# nocov start .onLoad <- function(...) { register_s3_method("dplyr", "union", "tbl_lazy") register_s3_method("dplyr", "intersect", "tbl_lazy") register_s3_method("dplyr", "setdiff", "tbl_lazy") register_s3_method("dplyr", "setdiff", "tbl_Oracle") register_s3_method("dplyr", "setdiff", "OraConnection") register_s3_method("dplyr", "filter", "tbl_lazy") register_s3_method("tidyr", "pivot_wider", "tbl_lazy") register_s3_method("tidyr", "pivot_longer", "tbl_lazy") register_s3_method("tidyr", "fill", "tbl_lazy") register_s3_method("tidyr", "complete", "tbl_lazy") register_s3_method("tidyr", "expand", "tbl_lazy") register_s3_method("tidyr", "replace_na", "tbl_lazy") if (utils::packageVersion("dplyr") >= "0.8.99") { register_s3_method("dplyr", "group_by_drop_default", "tbl_lazy") methods::setOldClass(c("ident_q", "ident", "character"), ident_q()) methods::setOldClass(c("ident", "character"), ident()) methods::setOldClass(c("sql", "character"), sql()) } base_scalar$`%>%` <- magrittr::`%>%` } # Silence R CMD check note: # ** checking whether the namespace can be loaded with stated dependencies ... NOTE # Warning in .undefineMethod("initialize", Class, classWhere) : # no generic function 'initialize' found # # I'm not sure why this is necessary, but I suspect it's due to the use of # setOldClass onLoad #' @importFrom methods initialize NULL register_s3_method <- function(pkg, generic, class, fun = NULL) { stopifnot(is.character(pkg), length(pkg) == 1) stopifnot(is.character(generic), length(generic) == 1) stopifnot(is.character(class), length(class) == 1) if (is.null(fun)) { fun <- get(paste0(generic, ".", class), envir = parent.frame()) } else { stopifnot(is.function(fun)) } if (pkg %in% loadedNamespaces()) { registerS3method(generic, class, fun, envir = asNamespace(pkg)) } # Always register hook in case package is later unloaded & reloaded setHook( packageEvent(pkg, "onLoad"), function(...) { registerS3method(generic, class, fun, envir = asNamespace(pkg)) } ) } # nocov end dbplyr/R/backend-odbc.R0000644000176200001440000000355114002647450014374 0ustar liggesusers#' Backend: ODBC #' #' @description #' See `vignette("translate-function")` and `vignette("translate-verb")` for #' details of overall translation technology. Key differences for this backend #' are minor translations for common data types. #' #' Use `simulate_odbc()` with `lazy_frame()` to see simulated SQL without #' converting to live access database. #' #' @name backend-odbc #' @aliases NULL #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' lf <- lazy_frame(a = TRUE, b = 1, d = 2, c = "z", con = simulate_odbc()) #' lf %>% transmute(x = as.numeric(b)) #' lf %>% transmute(x = as.integer(b)) #' lf %>% transmute(x = as.character(b)) NULL #' @export #' @rdname backend-odbc simulate_odbc <- function() simulate_dbi("OdbcConnection") #' @export dbplyr_edition.OdbcConnection <- function(con) { 2L } #' @export sql_translation.OdbcConnection <- function(con) { sql_variant( base_odbc_scalar, base_odbc_agg, base_odbc_win ) } #' @export #' @rdname sql_variant #' @format NULL base_odbc_scalar <- sql_translator( .parent = base_scalar, as.numeric = sql_cast("DOUBLE"), as.double = sql_cast("DOUBLE"), as.integer = sql_cast("INT"), as.character = sql_cast("STRING") ) #' @export #' @rdname sql_variant #' @format NULL base_odbc_agg <- sql_translator( .parent = base_agg, sd = sql_aggregate("STDDEV_SAMP", "sd") ) #' @export #' @rdname sql_variant #' @format NULL base_odbc_win <- sql_translator( .parent = base_win, sd = win_aggregate("STDDEV_SAMP"), ) #' @export db_connection_describe.OdbcConnection <- function(con) { info <- DBI::dbGetInfo(con) host <- if (info$servername == "") "localhost" else info$servername port <- if (info$port == "") "" else paste0(":", info$port) paste0( info$dbms.name, " ", info$db.version, "[", info$username, "@", host, port, "/", info$dbname, "]" ) } utils::globalVariables("EXP") dbplyr/R/sql-build.R0000644000176200001440000000600614002647450013772 0ustar liggesusers#' Build and render SQL from a sequence of lazy operations #' #' `sql_build()` creates a `select_query` S3 object, that is rendered #' to a SQL string by `sql_render()`. The output from `sql_build()` is #' designed to be easy to test, as it's database agnostic, and has #' a hierarchical structure. Outside of testing, however, you should #' always call `sql_render()`. #' #' `sql_build()` is generic over the lazy operations, \link{lazy_ops}, #' and generates an S3 object that represents the query. `sql_render()` #' takes a query object and then calls a function that is generic #' over the database. For example, `sql_build.op_mutate()` generates #' a `select_query`, and `sql_render.select_query()` calls #' `sql_select()`, which has different methods for different databases. #' The default methods should generate ANSI 92 SQL where possible, so you #' backends only need to override the methods if the backend is not ANSI #' compliant. #' #' @export #' @keywords internal #' @param op A sequence of lazy operations #' @param con A database connection. The default `NULL` uses a set of #' rules that should be very similar to ANSI 92, and allows for testing #' without an active database connection. #' @param ... Other arguments passed on to the methods. Not currently used. sql_build <- function(op, con = NULL, ...) { unique_subquery_name_reset() UseMethod("sql_build") } #' @export sql_build.tbl_lazy <- function(op, con = op$src$con, ...) { # only used for testing qry <- sql_build(op$ops, con = con, ...) sql_optimise(qry, con = con, ...) } #' @export sql_build.ident <- function(op, con = NULL, ...) { op } # Render ------------------------------------------------------------------ #' @export #' @rdname sql_build #' @param subquery Is this SQL going to be used in a subquery? #' This is important because you can place a bare table name in a subquery #' and ORDER BY does not work in subqueries. sql_render <- function(query, con = NULL, ..., subquery = FALSE) { UseMethod("sql_render") } #' @export sql_render.tbl_lazy <- function(query, con = query$src$con, ..., subquery = FALSE) { sql_render(query$ops, con = con, ..., subquery = subquery) } #' @export sql_render.op <- function(query, con = NULL, ..., subquery = FALSE) { qry <- sql_build(query, con = con, ...) qry <- sql_optimise(qry, con = con, ..., subquery = subquery) sql_render(qry, con = con, ..., subquery = subquery) } #' @export sql_render.sql <- function(query, con = NULL, ..., subquery = FALSE) { query } #' @export sql_render.ident <- function(query, con = NULL, ..., subquery = FALSE) { if (subquery) { query } else { dbplyr_query_select(con, sql("*"), query) } } # Optimise ---------------------------------------------------------------- #' @export #' @rdname sql_build sql_optimise <- function(x, con = NULL, ..., subquery = FALSE) { UseMethod("sql_optimise") } #' @export sql_optimise.sql <- function(x, con = NULL, ...) { # Can't optimise raw SQL x } #' @export sql_optimise.ident <- function(x, con = NULL, ...) { x } dbplyr/R/query-select.R0000644000176200001440000000725414002647450014526 0ustar liggesusers#' @export #' @rdname sql_build select_query <- function(from, select = sql("*"), where = character(), group_by = character(), having = character(), order_by = character(), limit = NULL, distinct = FALSE) { stopifnot(is.character(select)) stopifnot(is.character(where)) stopifnot(is.character(group_by)) stopifnot(is.character(having)) stopifnot(is.character(order_by)) stopifnot(is.null(limit) || (is.numeric(limit) && length(limit) == 1L)) stopifnot(is.logical(distinct), length(distinct) == 1L) structure( list( from = from, select = select, where = where, group_by = group_by, having = having, order_by = order_by, distinct = distinct, limit = limit ), class = c("select_query", "query") ) } #' @export print.select_query <- function(x, ...) { cat( "\n", sep = "" ) cat_line("From:") cat_line(indent_print(sql_build(x$from))) if (length(x$select)) cat("Select: ", named_commas(x$select), "\n", sep = "") if (length(x$where)) cat("Where: ", named_commas(x$where), "\n", sep = "") if (length(x$group_by)) cat("Group by: ", named_commas(x$group_by), "\n", sep = "") if (length(x$order_by)) cat("Order by: ", named_commas(x$order_by), "\n", sep = "") if (length(x$having)) cat("Having: ", named_commas(x$having), "\n", sep = "") if (length(x$limit)) cat("Limit: ", x$limit, "\n", sep = "") } #' @export sql_optimise.select_query <- function(x, con = NULL, ..., subquery = FALSE) { if (!inherits(x$from, "select_query")) { return(x) } from <- sql_optimise(x$from, subquery = subquery) # If all outer clauses are executed after the inner clauses, we # can drop them down a level outer <- select_query_clauses(x, subquery = subquery) inner <- select_query_clauses(from, subquery = TRUE) can_squash <- length(outer) == 0 || length(inner) == 0 || min(outer) > max(inner) if (can_squash) { # Have we optimised away an ORDER BY if (length(from$order_by) > 0 && length(x$order_by) > 0) { if (!identical(x$order_by, from$order_by)) { warn_drop_order_by() } } from[as.character(outer)] <- x[as.character(outer)] from } else { x$from <- from x } } # List clauses used by a query, in the order they are executed # https://sqlbolt.com/lesson/select_queries_order_of_execution # List clauses used by a query, in the order they are executed in select_query_clauses <- function(x, subquery = FALSE) { present <- c( where = length(x$where) > 0, group_by = length(x$group_by) > 0, having = length(x$having) > 0, select = !identical(x$select, sql("*")), distinct = x$distinct, order_by = (!subquery || !is.null(x$limit)) && length(x$order_by) > 0, limit = !is.null(x$limit) ) ordered(names(present)[present], levels = names(present)) } #' @export sql_render.select_query <- function(query, con, ..., subquery = FALSE) { from <- dbplyr_sql_subquery(con, sql_render(query$from, con, ..., subquery = TRUE), name = NULL ) dbplyr_query_select( con, query$select, from, where = query$where, group_by = query$group_by, having = query$having, order_by = query$order_by, limit = query$limit, distinct = query$distinct, ..., subquery = subquery ) } warn_drop_order_by <- function() { warn(c( "ORDER BY is ignored in subqueries without LIMIT", i = "Do you need to move arrange() later in the pipeline or use window_order() instead?" )) } dbplyr/R/backend-snowflake.R0000644000176200001440000000217014031447261015452 0ustar liggesusers#' Backend: Snowflake #' #' @description #' See `vignette("translate-function")` and `vignette("translate-verb")` for #' details of overall translation technology. #' #' Use `simulate_snowflake()` with `lazy_frame()` to see simulated SQL without #' converting to live access database. #' #' @name backend-snowflake #' @aliases NULL #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_snowflake()) #' lf %>% transmute(x = paste0(z, " times")) NULL #' @export sql_translation.Snowflake <- function(con) { sql_variant( sql_translator( .parent = base_odbc_scalar, log10 = function(x) sql_expr(log(10, !!x)) ), base_agg, base_win ) } #' @export #' @rdname backend-snowflake simulate_snowflake <- function() simulate_dbi("Snowflake") # There seems to be no concept of ANALYZE TABLE in Snowflake. I searched for # functions that performed similar operations, and found none. # Link to full list: https://docs.snowflake.com/en/sql-reference/sql-all.html #' @export sql_table_analyze.Snowflake <- function(con, table, ...) {} dbplyr/R/test-frame.R0000644000176200001440000000425714002647450014153 0ustar liggesusers#' Infrastructure for testing dplyr #' #' Register testing sources, then use `test_load()` to load an existing #' data frame into each source. To create a new table in each source, #' use `test_frame()`. #' #' @keywords internal #' @examples #' \dontrun{ #' test_register_src("sqlite", { #' DBI::dbConnect(RSQLite::SQLite(), ":memory:", create = TRUE) #' }) #' #' test_frame(x = 1:3, y = 3:1) #' test_load(mtcars) #' } #' @name testing NULL #' @export #' @rdname testing test_register_src <- function(name, src) { message("Registering testing src: ", name, " ", appendLF = FALSE) tryCatch( { test_srcs$add(name, src) message("OK") }, error = function(e) message("\n* ", conditionMessage(e)) ) } #' @export #' @rdname testing test_register_con <- function(name, ...) { test_register_src(name, DBI::dbConnect(...)) } #' @export #' @rdname testing src_test <- function(name) { srcs <- test_srcs$get() if (!name %in% names(srcs)) { testthat::skip(paste0("No ", name)) } else { srcs[[name]] } } #' @export #' @rdname testing test_load <- function(df, name = unique_table_name(), srcs = test_srcs$get(), ignore = character()) { stopifnot(is.data.frame(df)) stopifnot(is.character(ignore)) srcs <- srcs[setdiff(names(srcs), ignore)] lapply(srcs, copy_to, df, name = name) } #' @export #' @rdname testing test_frame <- function(..., srcs = test_srcs$get(), ignore = character()) { df <- tibble(...) test_load(df, srcs = srcs, ignore = ignore) } # Manage cache of testing srcs test_srcs <- local({ list( get = function() env_get(cache(), "srcs", list()), has = function(x) { srcs <- env_get(cache(), "srcs", list()) has_name(srcs, x) }, add = function(name, src) { srcs <- env_get(cache(), "srcs", list()) srcs[[name]] <- src env_poke(cache(), "srcs", srcs) }, set = function(...) { env_poke(cache(), "src", list(...)) }, length = function() { length(cache()$srcs) } ) }) # Modern helpers ---------------------------------------------------------- copy_to_test <- function(src, df, ...) { copy_to(src_test(src), df, "test", ..., overwrite = TRUE) } dbplyr/R/query-set-op.R0000644000176200001440000000166314002647450014454 0ustar liggesusers#' @export #' @rdname sql_build set_op_query <- function(x, y, type = type, all = FALSE) { structure( list( x = x, y = y, type = type, all = all ), class = c("set_op_query", "query") ) } #' @export print.set_op_query <- function(x, ...) { cat_line("") cat_line("X:") cat_line(indent_print(sql_build(x$x))) cat_line("Y:") cat_line(indent_print(sql_build(x$y))) } #' @export sql_render.set_op_query <- function(query, con = NULL, ..., subquery = FALSE) { from_x <- sql_render(query$x, con, ..., subquery = FALSE) from_y <- sql_render(query$y, con, ..., subquery = FALSE) if (dbplyr_edition(con) >= 2) { sql_query_set_op(con, from_x, from_y, method = query$type, all = query$all) } else { if (isTRUE(query$all)) { abort("`all` argument not supported by this backend") } dbplyr_query_set_op(con, from_x, from_y, method = query$type) } } dbplyr/R/verb-distinct.R0000644000176200001440000000204614004012136014640 0ustar liggesusers#' Subset distinct/unique rows #' #' This is a method for the dplyr [distinct()] generic. It adds the #' `DISTINCT` clause to the SQL query. #' #' @inheritParams arrange.tbl_lazy #' @inheritParams dplyr::distinct #' @inherit arrange.tbl_lazy return #' @export #' @importFrom dplyr distinct #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' db <- memdb_frame(x = c(1, 1, 2, 2), y = c(1, 2, 1, 1)) #' db %>% distinct() %>% show_query() #' db %>% distinct(x) %>% show_query() distinct.tbl_lazy <- function(.data, ..., .keep_all = FALSE) { if (dots_n(...) > 0) { if (.keep_all) { stop( "Can only find distinct value of specified columns if .keep_all is FALSE", call. = FALSE ) } .data <- transmute(.data, !!!syms(op_grps(.data)), ...) } add_op_single("distinct", .data, dots = list()) } #' @export op_vars.op_distinct <- function(op) { union(op_grps(op$x), op_vars(op$x)) } #' @export sql_build.op_distinct <- function(op, con, ...) { select_query( sql_build(op$x, con), distinct = TRUE ) } dbplyr/R/backend-sqlite.R0000644000176200001440000001200514004012136014745 0ustar liggesusers#' Backend: SQLite #' #' @description #' See `vignette("translate-function")` and `vignette("translate-verb")` for #' details of overall translation technology. Key differences for this backend #' are: #' #' * Uses non-standard `LOG()` function #' * Date-time extraction functions from lubridate #' * Custom median translation #' * Right and full joins are simulated using left joins #' #' Use `simulate_sqlite()` with `lazy_frame()` to see simulated SQL without #' converting to live access database. #' #' @name backend-sqlite #' @aliases NULL #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_sqlite()) #' lf %>% transmute(x = paste(c, " times")) #' lf %>% transmute(x = log(b), y = log(b, base = 2)) NULL #' @export #' @rdname backend-sqlite simulate_sqlite <- function() simulate_dbi("SQLiteConnection") #' @export dbplyr_edition.SQLiteConnection <- function(con) { 2L } #' @export db_connection_describe.SQLiteConnection <- function(con) { paste0("sqlite ", sqlite_version(), " [", con@dbname, "]") } #' @export sql_query_explain.SQLiteConnection <- function(con, sql, ...) { build_sql("EXPLAIN QUERY PLAN ", sql, con = con) } #' @export sql_query_set_op.SQLiteConnection <- function(con, x, y, method, ..., all = FALSE) { # SQLite does not allow parentheses build_sql( x, "\n", sql(method), if (all) sql(" ALL"), "\n", y, con = con ) } sqlite_version <- function() { numeric_version(RSQLite::rsqliteVersion()[[2]]) } # SQL methods ------------------------------------------------------------- #' @export sql_translation.SQLiteConnection <- function(con) { sql_variant( sql_translator(.parent = base_scalar, as.numeric = sql_cast("REAL"), as.double = sql_cast("REAL"), log = function(x, base = exp(1)) { if (base != exp(1)) { sql_expr(log(!!x) / log(!!base)) } else { sql_expr(log(!!x)) } }, paste = sql_paste_infix(" ", "||", function(x) sql_expr(cast(!!x %as% text))), paste0 = sql_paste_infix("", "||", function(x) sql_expr(cast(!!x %as% text))), # https://www.sqlite.org/lang_corefunc.html#maxoreunc pmin = sql_aggregate_n("MIN", "pmin"), pmax = sql_aggregate_n("MAX", "pmax"), # lubridate, today = function() { date <- function(x) {} # suppress R CMD check note sql_expr(date("now")) }, now = function() sql_expr(datetime("now")), # https://modern-sql.com/feature/extract#proprietary-strftime year = function(x) sql_expr(cast(strftime("%Y", !!x) %as% NUMERIC)), month = function(x) sql_expr(cast(strftime("%m", !!x) %as% NUMERIC)), mday = function(x) sql_expr(cast(strftime("%d", !!x) %as% NUMERIC)), day = function(x) sql_expr(cast(strftime("%d", !!x) %as% NUMERIC)), hour = function(x) sql_expr(cast(strftime("%H", !!x) %as% NUMERIC)), minute = function(x) sql_expr(cast(strftime("%M", !!x) %as% NUMERIC)), second = function(x) sql_expr(cast(strftime("%f", !!x) %as% REAL)), yday = function(x) sql_expr(cast(strftime("%j", !!x) %as% NUMERIC)), ), sql_translator(.parent = base_agg, sd = sql_aggregate("STDEV", "sd"), median = sql_aggregate("MEDIAN"), ), if (sqlite_version() >= "3.25") { sql_translator(.parent = base_win, sd = win_aggregate("STDEV"), median = win_absent("median") ) } else { base_no_win } ) } #' @export sql_escape_logical.SQLiteConnection <- function(con, x){ y <- as.character(as.integer(x)) y[is.na(x)] <- "NULL" y } #' @export sql_query_wrap.SQLiteConnection <- function(con, from, name = unique_subquery_name(), ...) { if (is.ident(from)) { setNames(from, name) } else { if (is.null(name)) { build_sql("(", from, ")", con = con) } else { build_sql("(", from, ") AS ", ident(name), con = con) } } } #' @export sql_expr_matches.SQLiteConnection <- function(con, x, y) { # https://sqlite.org/lang_expr.html#isisnot build_sql(x, " IS ", y, con = con) } #' @export sql_query_join.SQLiteConnection <- function(con, x, y, vars, type = "inner", by = NULL, na_matches = FALSE, ...) { # workaround as SQLite doesn't support FULL OUTER JOIN and RIGHT JOIN # see: https://www.sqlite.org/omitted.html # Careful: in the right join resp. the second join in the full join do not # name `y` LHS and `x` RHS as it messes up the select query! if (type == "full") { join_sql <- build_sql( sql_query_join(con, x, y, vars, type = "left", by = by, na_matches = na_matches, ...), "UNION\n", sql_query_join(con, y, x, vars, type = "left", by = by, na_matches = na_matches, ...), con = con ) sql_query_select( con, select = ident(vars$alias), from = sql_subquery(con, join_sql), subquery = TRUE ) } else if (type == "right") { sql_query_join(con, y, x, vars, type = "left", by = by, na_matches = na_matches, ...) } else { NextMethod() } } globalVariables(c("datetime", "NUMERIC", "REAL")) dbplyr/R/db-io.R0000644000176200001440000001230514002647450013067 0ustar liggesusers#' Database I/O generics #' #' @description #' These generics are responsible for getting data into and out of the #' database. They should be used a last resort - only use them when you can't #' make a backend work by providing methods for DBI generics, or for dbplyr's #' SQL generation generics. They tend to be most needed when a backend has #' special handling of temporary tables. #' #' * `db_copy_to()` implements [copy_to.src_sql()] by calling #' `db_write_table()` (which calls [DBI::dbWriteTable()]) to transfer the #' data, then optionally adds indexes (via [sql_table_index()]) and #' analyses (via [sql_table_analyze()]). #' #' * `db_compute()` implements [compute.tbl_sql()] by calling #' [sql_query_save()] to create the table, then optionally adds indexes #' (via [sql_table_index()]) and analyses (via [sql_table_analyze()]). #' #' * `db_collect()` implements [collect.tbl_sql()] using [DBI::dbSendQuery()] #' and [DBI::dbFetch()]. #' #' * `db_table_temporary()` is used for databases that have special naming #' schemes for temporary tables (e.g. SQL server and SAP HANA require #' temporary tables to start with `#`) #' #' @keywords internal #' @family generic #' @name db-io #' @aliases NULL NULL #' @export #' @rdname db-io db_copy_to <- function(con, table, values, overwrite = FALSE, types = NULL, temporary = TRUE, unique_indexes = NULL, indexes = NULL, analyze = TRUE, ..., in_transaction = TRUE) { UseMethod("db_copy_to") } #' @export db_copy_to.DBIConnection <- function(con, table, values, overwrite = FALSE, types = NULL, temporary = TRUE, unique_indexes = NULL, indexes = NULL, analyze = TRUE, ..., in_transaction = TRUE) { new <- db_table_temporary(con, table, temporary) table <- new$table temporary <- new$temporary with_transaction(con, in_transaction, { table <- dplyr::db_write_table(con, table, types = types, values = values, temporary = temporary, overwrite = overwrite ) create_indexes(con, table, unique_indexes, unique = TRUE) create_indexes(con, table, indexes) if (analyze) dbplyr_analyze(con, table) }) table } #' @export #' @rdname db-io db_compute <- function(con, table, sql, temporary = TRUE, unique_indexes = list(), indexes = list(), analyze = TRUE, ...) { UseMethod("db_compute") } #' @export db_compute.DBIConnection <- function(con, table, sql, temporary = TRUE, unique_indexes = list(), indexes = list(), analyze = TRUE, ...) { new <- db_table_temporary(con, table, temporary) table <- new$table temporary <- new$temporary table <- dbplyr_save_query(con, sql, table, temporary = temporary) create_indexes(con, table, unique_indexes, unique = TRUE) create_indexes(con, table, indexes) if (analyze) dbplyr_analyze(con, table) table } #' @export #' @rdname db-io db_collect <- function(con, sql, n = -1, warn_incomplete = TRUE, ...) { UseMethod("db_collect") } #' @export db_collect.DBIConnection <- function(con, sql, n = -1, warn_incomplete = TRUE, ...) { res <- dbSendQuery(con, sql) tryCatch({ out <- dbFetch(res, n = n) if (warn_incomplete) { res_warn_incomplete(res, "n = Inf") } }, finally = { dbClearResult(res) }) out } #' @export #' @importFrom dplyr db_write_table db_write_table.DBIConnection <- function(con, table, types, values, temporary = TRUE, overwrite = FALSE, ...) { dbWriteTable( con, name = dbi_quote(table, con), value = values, field.types = types, temporary = temporary, overwrite = overwrite, row.names = FALSE ) table } # Utility functions ------------------------------------------------------------ dbi_quote <- function(x, con) UseMethod("dbi_quote") dbi_quote.ident <- function(x, con) DBI::dbQuoteIdentifier(con, as.character(x)) dbi_quote.character <- function(x, con) DBI::dbQuoteString(con, x) dbi_quote.sql <- function(x, con) DBI::SQL(as.character(x)) create_indexes <- function(con, table, indexes = NULL, unique = FALSE, ...) { if (is.null(indexes)) { return() } indexes <- as.list(indexes) for (index in indexes) { dbplyr_create_index(con, table, index, unique = unique, ...) } } # Don't use `tryCatch()` because it messes with the callstack with_transaction <- function(con, in_transaction, code) { if (in_transaction) { dbBegin(con) on.exit(dbRollback(con)) } code if (in_transaction) { on.exit() dbCommit(con) } } #' @export #' @rdname db-io db_table_temporary <- function(con, table, temporary) { UseMethod("db_table_temporary") } #' @export db_table_temporary.DBIConnection <- function(con, table, temporary) { list( table = table, temporary = temporary ) } dbplyr/R/partial-eval.R0000644000176200001440000002203114015731174014454 0ustar liggesusers#' Partially evaluate an expression. #' #' This function partially evaluates an expression, using information from #' the tbl to determine whether names refer to local expressions #' or remote variables. This simplifies SQL translation because expressions #' don't need to carry around their environment - all relevant information #' is incorporated into the expression. #' #' @section Symbol substitution: #' #' `partial_eval()` needs to guess if you're referring to a variable on the #' server (remote), or in the current environment (local). It's not possible to #' do this 100% perfectly. `partial_eval()` uses the following heuristic: #' #' \itemize{ #' \item If the tbl variables are known, and the symbol matches a tbl #' variable, then remote. #' \item If the symbol is defined locally, local. #' \item Otherwise, remote. #' } #' #' You can override the guesses using `local()` and `remote()` to force #' computation, or by using the `.data` and `.env` pronouns of tidy evaluation. #' #' @param call an unevaluated expression, as produced by [quote()] #' @param vars character vector of variable names. #' @param env environment in which to search for local values #' @export #' @keywords internal #' @examples #' vars <- c("year", "id") #' partial_eval(quote(year > 1980), vars = vars) #' #' ids <- c("ansonca01", "forceda01", "mathebo01") #' partial_eval(quote(id %in% ids), vars = vars) #' #' # cf. #' partial_eval(quote(id == .data$ids), vars = vars) #' #' # You can use local() or .env to disambiguate between local and remote #' # variables: otherwise remote is always preferred #' year <- 1980 #' partial_eval(quote(year > year), vars = vars) #' partial_eval(quote(year > local(year)), vars = vars) #' partial_eval(quote(year > .env$year), vars = vars) #' #' # Functions are always assumed to be remote. Use local to force evaluation #' # in R. #' f <- function(x) x + 1 #' partial_eval(quote(year > f(1980)), vars = vars) #' partial_eval(quote(year > local(f(1980))), vars = vars) #' #' # For testing you can also use it with the tbl omitted #' partial_eval(quote(1 + 2 * 3)) #' x <- 1 #' partial_eval(quote(x ^ y)) partial_eval <- function(call, vars = character(), env = caller_env()) { if (is_null(call)) { NULL } else if (is_atomic(call) || blob::is_blob(call)) { call } else if (is_symbol(call)) { partial_eval_sym(call, vars, env) } else if (is_quosure(call)) { partial_eval(get_expr(call), vars, get_env(call)) } else if (is_call(call)) { partial_eval_call(call, vars, env) } else { abort(glue("Unknown input type: ", typeof(call))) } } partial_eval_dots <- function(dots, vars) { stopifnot(inherits(dots, "quosures")) dots <- lapply(dots, partial_eval_quo, vars = vars) # Flatten across() calls is_list <- vapply(dots, is.list, logical(1)) dots[!is_list] <- lapply(dots[!is_list], list) names(dots)[is_list] <- "" unlist(dots, recursive = FALSE) } partial_eval_quo <- function(x, vars) { expr <- partial_eval(get_expr(x), vars, get_env(x)) if (is.list(expr)) { lapply(expr, new_quosure, env = get_env(x)) } else { new_quosure(expr, get_env(x)) } } partial_eval_sym <- function(sym, vars, env) { name <- as_string(sym) if (name %in% vars) { sym } else if (env_has(env, name, inherit = TRUE)) { eval_bare(sym, env) } else { sym } } is_namespaced_dplyr_call <- function(call) { is_symbol(call[[1]], "::") && is_symbol(call[[2]], "dplyr") } is_tidy_pronoun <- function(call) { is_symbol(call[[1]], c("$", "[[")) && is_symbol(call[[2]], c(".data", ".env")) } partial_eval_call <- function(call, vars, env) { fun <- call[[1]] # Try to find the name of inlined functions if (inherits(fun, "inline_colwise_function")) { dot_var <- vars[[attr(call, "position")]] call <- replace_dot(attr(fun, "formula")[[2]], dot_var) env <- get_env(attr(fun, "formula")) } else if (is.function(fun)) { fun_name <- find_fun(fun) if (is.null(fun_name)) { # This probably won't work, but it seems like it's worth a shot. return(eval_bare(call, env)) } call[[1]] <- fun <- sym(fun_name) } # So are compound calls, EXCEPT dplyr::foo() if (is.call(fun)) { if (is_namespaced_dplyr_call(fun)) { call[[1]] <- fun[[3]] } else if (is_tidy_pronoun(fun)) { stop("Use local() or remote() to force evaluation of functions", call. = FALSE) } else { return(eval_bare(call, env)) } } # .data$, .data[[]], .env$, .env[[]] need special handling if (is_tidy_pronoun(call)) { if (is_symbol(call[[1]], "$")) { idx <- call[[3]] } else { idx <- as.name(eval_bare(call[[3]], env)) } if (is_symbol(call[[2]], ".data")) { idx } else { eval_bare(idx, env) } } else if (is_call(call, "if_any")) { db_squash_if(call, env, vars, reduce = "|") } else if (is_call(call, "if_all")) { db_squash_if(call, env, vars, reduce = "&") } else if (is_call(call, "across")) { partial_eval_across(call, vars, env) } else { # Process call arguments recursively, unless user has manually called # remote/local name <- as_string(call[[1]]) if (name == "local") { eval_bare(call[[2]], env) } else if (name == "remote") { call[[2]] } else { call[-1] <- lapply(call[-1], partial_eval, vars = vars, env = env) call } } } partial_eval_across <- function(call, vars, env) { call <- match.call(dplyr::across, call, expand.dots = FALSE, envir = env) tbl <- as_tibble(rep_named(vars, list(logical()))) cols <- syms(vars)[tidyselect::eval_select(call$.cols, tbl, allow_rename = FALSE)] funs <- across_funs(call$.fns, env) # Generate grid of expressions out <- vector("list", length(cols) * length(funs)) k <- 1 for (i in seq_along(cols)) { for (j in seq_along(funs)) { out[[k]] <- exec(funs[[j]], cols[[i]], !!!call$...) k <- k + 1 } } .names <- eval(call$.names, env) names(out) <- across_names(cols, names(funs), .names, env) out } capture_if_all <- function(data, x) { x <- enquo(x) db_squash_if(get_expr(x), get_env(x), colnames(data)) } db_squash_if <- function(call, env, vars, reduce = "&") { call <- match.call(dplyr::if_any, call, expand.dots = FALSE, envir = env) tbl <- as_tibble(rep_named(vars, list(logical()))) locs <- tidyselect::eval_select(call$.cols, tbl, allow_rename = FALSE) cols <- syms(names(tbl))[locs] fun <- across_fun(call$.fns, env) out <- vector("list", length(cols)) for (i in seq_along(cols)) { out[[i]] <- exec(fun, cols[[i]], !!!call$...) } Reduce(function(x, y) call2(reduce, x, y), out) } across_funs <- function(funs, env = caller_env()) { if (is.null(funs)) { list(function(x, ...) x) } else if (is_symbol(funs)) { set_names(list(across_fun(funs, env)), as.character(funs)) } else if (is.character(funs)) { names(funs)[names2(funs) == ""] <- funs lapply(funs, across_fun, env) } else if (is_call(funs, "~")) { set_names(list(across_fun(funs, env)), expr_name(f_rhs(funs))) } else if (is_call(funs, "list")) { args <- rlang::exprs_auto_name(funs[-1]) lapply(args, across_fun, env) } else if (!is.null(env)) { # Try evaluating once, just in case funs <- eval(funs, env) across_funs(funs, NULL) } else { abort("`.fns` argument to dbplyr::across() must be a NULL, a function name, formula, or list") } } across_fun <- function(fun, env) { if (is_symbol(fun) || is_string(fun)) { function(x, ...) call2(fun, x, ...) } else if (is_call(fun, "~")) { fun <- across_formula_fn(f_rhs(fun)) function(x, ...) expr_interp(fun, child_env(emptyenv(), .x = x)) } else { abort(c( ".fns argument to dbplyr::across() contain a function name or a formula", x = paste0("Problem with ", expr_deparse(fun)) )) } } across_formula_fn <- function(x) { if (is_symbol(x, ".") || is_symbol(x, ".x")) { quote(!!.x) } else if (is_call(x)) { x[-1] <- lapply(x[-1], across_formula_fn) x } else { x } } across_names <- function(cols, funs, names = NULL, env = parent.frame()) { if (length(funs) == 1) { names <- names %||% "{.col}" } else { names <- names %||% "{.col}_{.fn}" } glue_env <- child_env(env, .col = rep(cols, each = length(funs)), .fn = rep(funs %||% seq_along(funs), length(cols)) ) glue(names, .envir = glue_env) } find_fun <- function(fun) { if (is_lambda(fun)) { body <- body(fun) if (!is_call(body)) { return(NULL) } fun_name <- body[[1]] if (!is_symbol(fun_name)) { return(NULL) } as.character(fun_name) } else if (is.function(fun)) { fun_name(fun) } } fun_name <- function(fun) { pkg_env <- env_parent(global_env()) known <- c(ls(base_agg), ls(base_scalar)) for (x in known) { if (!env_has(pkg_env, x, inherit = TRUE)) next fun_x <- env_get(pkg_env, x, inherit = TRUE) if (identical(fun, fun_x)) return(x) } NULL } replace_dot <- function(call, var) { if (is_symbol(call, ".")) { sym(var) } else if (is_call(call)) { call[] <- lapply(call, replace_dot, var = var) call } else { call } } dbplyr/R/backend-mysql.R0000644000176200001440000001027114015731174014630 0ustar liggesusers#' Backend: MySQL/MariaDB #' #' @description #' See `vignette("translate-function")` and `vignette("translate-verb")` for #' details of overall translation technology. Key differences for this backend #' are: #' #' * `paste()` uses `CONCAT_WS()` #' * String translations for `str_detect()`, `str_locate()`, and #' `str_replace_all()` #' * Clear error message for unsupported full joins #' #' Use `simulate_mysql()` with `lazy_frame()` to see simulated SQL without #' converting to live access database. #' #' @name backend-mysql #' @aliases NULL #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_mysql()) #' lf %>% transmute(x = paste0(z, " times")) NULL #' @export #' @rdname backend-mysql simulate_mysql <- function() simulate_dbi("MariaDBConnection") #' @export dbplyr_edition.MariaDBConnection <- function(con) { 2L } #' @export dbplyr_edition.MySQL <- dbplyr_edition.MariaDBConnection #' @export dbplyr_edition.MySQLConnection <- dbplyr_edition.MariaDBConnection #' @export db_connection_describe.MariaDBConnection <- function(con) { info <- dbGetInfo(con) paste0( "mysql ", info$serverVersion, " [", info$username, "@", info$host, ":", info$port, "/", info$dbname, "]" ) } #' @export db_connection_describe.MySQL <- db_connection_describe.MariaDBConnection #' @export db_connection_describe.MySQLConnection <- db_connection_describe.MariaDBConnection #' @export sql_translation.MariaDBConnection <- function(con) { sql_variant( sql_translator(.parent = base_scalar, as.logical = function(x) { sql_expr(IF(x, TRUE, FALSE)) }, as.character = sql_cast("CHAR"), # string functions ------------------------------------------------ paste = sql_paste(" "), paste0 = sql_paste(""), # stringr str_c = sql_paste(""), # https://dev.mysql.com/doc/refman/8.0/en/regexp.html # NB: case insensitive by default; could use REGEXP_LIKE for MySQL, # but available in MariaDB. A few more details at: # https://www.oreilly.com/library/view/mysql-cookbook/0596001452/ch04s11.html str_detect = sql_infix("REGEXP"), str_locate = function(string, pattern) { sql_expr(REGEXP_INSTR(!!string, !!pattern)) }, str_replace_all = function(string, pattern, replacement){ sql_expr(regexp_replace(!!string, !!pattern, !!replacement)) } ), sql_translator(.parent = base_agg, sd = sql_aggregate("STDDEV_SAMP", "sd"), var = sql_aggregate("VAR_SAMP", "var"), str_flatten = function(x, collapse) { sql_expr(group_concat(!!x %separator% !!collapse)) } ), sql_translator(.parent = base_win, sd = win_aggregate("STDDEV_SAMP"), var = win_aggregate("VAR_SAMP"), # GROUP_CONCAT not currently available as window function # https://mariadb.com/kb/en/library/aggregate-functions-as-window-functions/ str_flatten = win_absent("str_flatten") ) ) } #' @export sql_translation.MySQL <- sql_translation.MariaDBConnection #' @export sql_translation.MySQLConnection <- sql_translation.MariaDBConnection #' @export sql_table_analyze.MariaDBConnection <- function(con, table, ...) { build_sql("ANALYZE TABLE ", as.sql(table, con = con), con = con) } #' @export sql_table_analyze.MySQL <- sql_table_analyze.MariaDBConnection #' @export sql_table_analyze.MySQLConnection <- sql_table_analyze.MariaDBConnection #' @export sql_query_join.MariaDBConnection <- function(con, x, y, vars, type = "inner", by = NULL, ...) { if (identical(type, "full")) { stop("MySQL does not support full joins", call. = FALSE) } NextMethod() } #' @export sql_query_join.MySQL <- sql_query_join.MariaDBConnection #' @export sql_query_join.MySQLConnection <- sql_query_join.MariaDBConnection #' @export sql_expr_matches.MariaDBConnection <- function(con, x, y) { # https://dev.mysql.com/doc/refman/5.7/en/comparison-operators.html#operator_equal-to build_sql(x, " <=> ", y, con = con) } #' @export sql_expr_matches.MySQL <- sql_expr_matches.MariaDBConnection #' @export sql_expr_matches.MySQLConnection <- sql_expr_matches.MariaDBConnection globalVariables(c("%separator%", "group_concat", "IF", "REGEXP_INSTR")) dbplyr/R/tbl-sql.R0000644000176200001440000000440614002647450013456 0ustar liggesusers#' Create an SQL tbl (abstract) #' #' Generally, you should no longer need to provide a custom `tbl()` #' method. #' The default `tbl.DBIConnect` method should work in most cases. #' #' @keywords internal #' @export #' @param subclass name of subclass #' @param ... needed for agreement with generic. Not otherwise used. #' @param vars Provide column names as a character vector #' to avoid retrieving them from the database. #' Mainly useful for better performance when creating #' multiple `tbl` objects. tbl_sql <- function(subclass, src, from, ..., vars = NULL) { # If not literal sql, must be a table identifier from <- as.sql(from, con = src$con) vars <- vars %||% dbplyr_query_fields(src$con, from) ops <- op_base_remote(from, vars) dplyr::make_tbl(c(subclass, "sql", "lazy"), src = src, ops = ops) } #' @importFrom dplyr same_src #' @export same_src.tbl_sql <- function(x, y) { inherits(y, "tbl_sql") && same_src(x$src, y$src) } # Grouping methods ------------------------------------------------------------- #' @importFrom dplyr group_size #' @export group_size.tbl_sql <- function(x) { df <- x %>% summarise(n = n()) %>% collect() df$n } #' @importFrom dplyr n_groups #' @export n_groups.tbl_sql <- function(x) { if (length(groups(x)) == 0) return(1L) df <- x %>% summarise() %>% ungroup() %>% summarise(n = n()) %>% collect() df$n } # Standard data frame methods -------------------------------------------------- #' @export print.tbl_sql <- function(x, ..., n = NULL, width = NULL, n_extra = NULL) { cat_line(format(x, ..., n = n, width = width, n_extra = n_extra)) invisible(x) } #' @export as.data.frame.tbl_sql <- function(x, row.names = NULL, optional = NULL, ..., n = Inf) { as.data.frame(collect(x, n = n)) } #' @export #' @importFrom tibble tbl_sum tbl_sum.tbl_sql <- function(x) { grps <- op_grps(x$ops) sort <- op_sort(x$ops) c( "Source" = tbl_desc(x), "Database" = dbplyr_connection_describe(x$src$con), "Groups" = if (length(grps) > 0) commas(grps), "Ordered by" = if (length(sort) > 0) commas(deparse_all(sort)) ) } tbl_desc <- function(x) { paste0( op_desc(x$ops), " [", op_rows(x$ops), " x ", big_mark(op_cols(x$ops)), "]" ) } dbplyr/R/verb-pivot-wider.R0000644000176200001440000001726014006531012015275 0ustar liggesusers#' Pivot data from long to wide #' #' `pivot_wider()` "widens" data, increasing the number of columns and #' decreasing the number of rows. The inverse transformation is #' `pivot_longer()`. #' Learn more in `vignette("pivot", "tidyr")`. #' #' @details #' The big difference to `pivot_wider()` for local data frames is that #' `values_fn` must not be `NULL`. By default it is `max()` which yields #' the same results as for local data frames if the combination of `id_cols` #' and `value` column uniquely identify an observation. #' Mind that you also do not get a warning if an observation is not uniquely #' identified. #' #' The translation to SQL code basically works as follows: #' #' 1. Get unique keys in `names_from` column. #' 2. For each key value generate an expression of the form: #' ```sql #' value_fn( #' CASE WHEN (`names from column` == `key value`) #' THEN (`value column`) #' END #' ) AS `output column` #' ``` #' 3. Group data by id columns. #' 4. Summarise the grouped data with the expressions from step 2. #' #' @param data A lazy data frame backed by a database query. #' @param id_cols A set of columns that uniquely identifies each observation. #' @param names_from,values_from A pair of #' arguments describing which column (or columns) to get the name of the #' output column (`names_from`), and which column (or columns) to get the #' cell values from (`values_from`). #' #' If `values_from` contains multiple values, the value will be added to the #' front of the output column. #' @param names_prefix String added to the start of every variable name. #' @param names_sep If `names_from` or `values_from` contains multiple #' variables, this will be used to join their values together into a single #' string to use as a column name. #' @param names_glue Instead of `names_sep` and `names_prefix`, you can supply #' a glue specification that uses the `names_from` columns (and special #' `.value`) to create custom column names. #' @param names_sort Should the column names be sorted? If `FALSE`, the default, #' column names are ordered by first appearance. #' @param names_repair What happens if the output has invalid column names? #' @param values_fill Optionally, a (scalar) value that specifies what each #' `value` should be filled in with when missing. #' @param values_fn A function, the default is `max()`, applied to the `value` #' in each cell in the output. In contrast to local data frames it must not be #' `NULL`. #' @param ... Unused; included for compatibility with generic. #' #' @examples #' if (require("tidyr", quietly = TRUE)) { #' memdb_frame( #' id = 1, #' key = c("x", "y"), #' value = 1:2 #' ) %>% #' tidyr::pivot_wider( #' id_cols = id, #' names_from = key, #' values_from = value #' ) #' } pivot_wider.tbl_lazy <- function(data, id_cols = NULL, names_from = name, names_prefix = "", names_sep = "_", names_glue = NULL, names_sort = FALSE, names_repair = "check_unique", values_from = value, values_fill = NULL, values_fn = max, ... ) { ellipsis::check_dots_empty() names_from <- enquo(names_from) values_from <- enquo(values_from) spec <- dbplyr_build_wider_spec(data, names_from = !!names_from, values_from = !!values_from, names_prefix = names_prefix, names_sep = names_sep, names_glue = names_glue, names_sort = names_sort ) id_cols <- enquo(id_cols) dbplyr_pivot_wider_spec(data, spec, !!id_cols, names_repair = names_repair, values_fill = values_fill, values_fn = values_fn ) } dbplyr_build_wider_spec <- function(data, names_from = name, values_from = value, names_prefix = "", names_sep = "_", names_glue = NULL, names_sort = FALSE) { if (!inherits(data, "tbl_sql")) { error_message <- c( "`dbplyr_build_wider_spec()` doesn't work with local lazy tibbles.", i = "Use `memdb_frame()` together with `show_query()` to see the SQL code." ) abort(error_message) } # prepare a minimal local tibble we can pass to `tidyr::build_wider_spec` # 1. create a tibble with unique values in the `names_from` column # row_ids <- vec_unique(data[names_from]) sim_data <- simulate_vars(data) names_from <- tidyselect::eval_select(enquo(names_from), sim_data) %>% names() distinct_data <- collect(distinct(data, !!!syms(names_from))) # 2. add `values_from` column values_from <- tidyselect::eval_select(enquo(values_from), sim_data) %>% names() dummy_data <- vctrs::vec_cbind( distinct_data, !!!rlang::rep_named(values_from, list(TRUE)), .name_repair = "check_unique" ) tidyr::build_wider_spec(dummy_data, names_from = !!enquo(names_from), values_from = !!enquo(values_from), names_prefix = names_prefix, names_sep = names_sep, names_glue = names_glue, names_sort = names_sort ) } dbplyr_pivot_wider_spec <- function(data, spec, names_repair = "check_unique", id_cols = NULL, values_fill = NULL, values_fn = max) { spec <- check_spec(spec) if (is.null(values_fn)) { abort(c( "`values_fn` must not be NULL", i = "`values_fn` must be a function or a named list of functions" )) } if (is.function(values_fn)) { values_fn <- rep_named(unique(spec$.value), list(values_fn)) values_fn <- purrr::map_chr(values_fn, find_fun) } if (is_scalar(values_fill)) { values_fill <- rep_named(unique(spec$.value), list(values_fill)) } if (!is.null(values_fill) && !is.list(values_fill)) { abort("`values_fill` must be NULL, a scalar, or a named list") } values <- vctrs::vec_unique(spec$.value) spec_cols <- c(names(spec)[-(1:2)], values) id_cols <- enquo(id_cols) if (!quo_is_null(id_cols)) { cn <- set_names(colnames(data)) key_vars <- names(tidyselect::eval_select(enquo(id_cols), cn)) } else { key_vars <- tbl_vars(data) } key_vars <- setdiff(key_vars, spec_cols) key_col <- sym(names(spec)[3]) pivot_exprs <- purrr::map( vctrs::vec_seq_along(spec), function(row) { key <- spec[[3]][row] values_col <- spec[[".value"]][row] fill_value <- values_fill[[values_col]] case_expr <- expr(ifelse(!!key_col == !!key, !!sym(values_col), !!fill_value)) agg_fn <- values_fn[[values_col]] expr((!!agg_fn)(!!case_expr, na.rm = TRUE)) } ) %>% set_names(spec$.name) data_grouped <- group_by(data, !!!syms(key_vars), .add = TRUE) group_names <- group_vars(data_grouped) out_nms <- c(group_names, names(pivot_exprs)) out_nms_repaired <- vctrs::vec_as_names(out_nms, repair = names_repair) if (!is_empty(group_names)) { out_nms_repaired <- out_nms_repaired[-(1:length(group_names))] } pivot_exprs <- set_names(pivot_exprs, out_nms_repaired) data_grouped %>% summarise(!!!pivot_exprs, .groups = "drop") %>% group_by(!!!syms(group_vars(data))) } globalVariables(c("name", "value")) is_scalar <- function(x) { if (is.null(x)) { return(FALSE) } if (is.list(x)) { (length(x) == 1) && !have_name(x) } else { length(x) == 1 } } dbplyr/R/verb-copy-to.R0000644000176200001440000000673514002647450014435 0ustar liggesusers#' Copy a local data frame to a remote database #' #' @description #' This is an implementation of the dplyr [copy_to()] generic and it mostly #' a wrapper around [DBI::dbWriteTable()]. #' #' It is useful for copying small amounts of data to a database for examples, #' experiments, and joins. By default, it creates temporary tables which are #' only visible within the current connection to the database. #' #' @export #' @param df A local data frame, a `tbl_sql` from same source, or a `tbl_sql` #' from another source. If from another source, all data must transition #' through R in one pass, so it is only suitable for transferring small #' amounts of data. #' @param types a character vector giving variable types to use for the columns. #' See for available types. #' @param temporary if `TRUE`, will create a temporary table that is #' local to this connection and will be automatically deleted when the #' connection expires #' @param unique_indexes a list of character vectors. Each element of the list #' will create a new unique index over the specified column(s). Duplicate rows #' will result in failure. #' @param indexes a list of character vectors. Each element of the list #' will create a new index. #' @param analyze if `TRUE` (the default), will automatically ANALYZE the #' new table so that the query optimiser has useful information. #' @param in_transaction Should the table creation be wrapped in a transaction? #' This typically makes things faster, but you may want to suppress if the #' database doesn't support transactions, or you're wrapping in a transaction #' higher up (and your database doesn't support nested transactions.) #' @inheritParams dplyr::copy_to #' @inherit arrange.tbl_lazy return #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' df <- data.frame(x = 1:5, y = letters[5:1]) #' db <- copy_to(src_memdb(), df) #' db #' #' df2 <- data.frame(y = c("a", "d"), fruit = c("apple", "date")) #' # copy_to() is called automatically if you set copy = TRUE #' # in the join functions #' db %>% left_join(df2, copy = TRUE) #' @importFrom dplyr copy_to copy_to.src_sql <- function(dest, df, name = deparse(substitute(df)), overwrite = FALSE, types = NULL, temporary = TRUE, unique_indexes = NULL, indexes = NULL, analyze = TRUE, ..., in_transaction = TRUE ) { assert_that(is.flag(temporary)) if (!is.data.frame(df) && !inherits(df, "tbl_sql")) { stop("`df` must be a local dataframe or a remote tbl_sql", call. = FALSE) } name <- as.sql(name, con = dest$con) if (inherits(df, "tbl_sql") && same_src(df$src, dest)) { out <- compute(df, name = name, temporary = temporary, unique_indexes = unique_indexes, indexes = indexes, analyze = analyze, ... ) } else { # avoid S4 dispatch problem in dbSendPreparedQuery df <- as.data.frame(collect(df)) name <- db_copy_to(dest$con, name, df, overwrite = overwrite, types = types, temporary = temporary, unique_indexes = unique_indexes, indexes = indexes, analyze = analyze, in_transaction = in_transaction, ... ) out <- tbl(dest, name) } invisible(out) } #' @importFrom dplyr auto_copy #' @export auto_copy.tbl_sql <- function(x, y, copy = FALSE, ...) { copy_to(x$src, as.data.frame(y), unique_table_name(), ...) } dbplyr/R/backend-.R0000644000176200001440000003452714031447261013553 0ustar liggesusers#' @include translate-sql-conditional.R #' @include translate-sql-window.R #' @include translate-sql-helpers.R #' @include translate-sql-paste.R #' @include translate-sql-string.R #' @include translate-sql-quantile.R #' @include escape.R #' @include sql.R #' @include utils.R NULL #' @export sql_translation.DBIConnection <- function(con) { sql_variant( base_scalar, base_agg, base_win ) } #' @export #' @rdname sql_variant #' @format NULL base_scalar <- sql_translator( `+` = sql_infix("+"), `*` = sql_infix("*"), `/` = sql_infix("/"), `%/%` = sql_not_supported("%/%"), `%%` = sql_infix("%"), `^` = sql_prefix("POWER", 2), `-` = function(x, y = NULL) { if (is.null(y)) { if (is.numeric(x)) { -x } else { sql_expr(-!!x) } } else { sql_expr(!!x - !!y) } }, `$` = sql_infix(".", pad = FALSE), `[[` = function(x, i) { i <- enexpr(i) if (is.character(i)) { build_sql(x, ".", ident(i)) } else if (is.numeric(i)) { build_sql(x, "[", as.integer(i), "]") } else { stop("Can only index with strings and numbers", call. = FALSE) } }, `[` = function(x, i) { build_sql("CASE WHEN (", i, ") THEN (", x, ") END") }, `!=` = sql_infix("!="), `==` = sql_infix("="), `<` = sql_infix("<"), `<=` = sql_infix("<="), `>` = sql_infix(">"), `>=` = sql_infix(">="), `%in%` = function(x, table) { if (is.sql(table) || length(table) > 1) { sql_expr(!!x %in% !!table) } else if (length(table) == 0) { sql_expr(FALSE) } else { sql_expr(!!x %in% ((!!table))) } }, `!` = sql_prefix("NOT"), `&` = sql_infix("AND"), `&&` = sql_infix("AND"), `|` = sql_infix("OR"), `||` = sql_infix("OR"), xor = function(x, y) { sql_expr(!!x %OR% !!y %AND NOT% (!!x %AND% !!y)) }, # bitwise operators # SQL Syntax reference links: # Hive: https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF#LanguageManualUDF-ArithmeticOperators # Impala: https://www.cloudera.com/documentation/enterprise/5-9-x/topics/impala_bit_functions.html # PostgreSQL: https://www.postgresql.org/docs/7.4/functions-math.html # MS SQL: https://docs.microsoft.com/en-us/sql/t-sql/language-elements/bitwise-operators-transact-sql?view=sql-server-2017 # MySQL https://dev.mysql.com/doc/refman/5.7/en/bit-functions.html # Oracle: https://docs.oracle.com/cd/E19253-01/817-6223/chp-typeopexpr-7/index.html # SQLite: https://www.tutorialspoint.com/sqlite/sqlite_bitwise_operators.htm # Teradata: https://docs.teradata.com/reader/1DcoER_KpnGTfgPinRAFUw/h3CS4MuKL1LCMQmnubeSRQ bitwNot = function(x) sql_expr(~ ((!!x))), bitwAnd = sql_infix("&"), bitwOr = sql_infix("|"), bitwXor = sql_infix("^"), bitwShiftL = sql_infix("<<"), bitwShiftR = sql_infix(">>"), abs = sql_prefix("ABS", 1), acos = sql_prefix("ACOS", 1), asin = sql_prefix("ASIN", 1), atan = sql_prefix("ATAN", 1), atan2 = sql_prefix("ATAN2", 2), ceil = sql_prefix("CEIL", 1), ceiling = sql_prefix("CEIL", 1), cos = sql_prefix("COS", 1), cot = sql_prefix("COT", 1), exp = sql_prefix("EXP", 1), floor = sql_prefix("FLOOR", 1), log = function(x, base = exp(1)) { if (isTRUE(all.equal(base, exp(1)))) { sql_expr(ln(!!x)) } else { sql_expr(log(!!base, !!x)) } }, log10 = sql_prefix("LOG10", 1), round = sql_prefix("ROUND", 2), sign = sql_prefix("SIGN", 1), sin = sql_prefix("SIN", 1), sqrt = sql_prefix("SQRT", 1), tan = sql_prefix("TAN", 1), # cosh, sinh, coth and tanh calculations are based on this article # https://en.wikipedia.org/wiki/Hyperbolic_function cosh = function(x) sql_expr((!!sql_exp(1, x) + !!sql_exp(-1, x)) / 2L), sinh = function(x) sql_expr((!!sql_exp(1, x) - !!sql_exp(-1, x)) / 2L), tanh = function(x) sql_expr((!!sql_exp(2, x) - 1L) / (!!sql_exp(2, x) + 1L)), coth = function(x) sql_expr((!!sql_exp(2, x) + 1L) / (!!sql_exp(2, x) - 1L)), round = function(x, digits = 0L) { sql_expr(ROUND(!!x, !!as.integer(digits))) }, `if` = sql_if, if_else = function(condition, true, false) sql_if(condition, true, false), ifelse = function(test, yes, no) sql_if(test, yes, no), switch = function(x, ...) sql_switch(x, ...), case_when = function(...) sql_case_when(...), sql = function(...) sql(...), `(` = function(x) { sql_expr(((!!x))) }, `{` = function(x) { sql_expr(((!!x))) }, desc = function(x) { build_sql(x, sql(" DESC")) }, is.null = sql_is_null, is.na = sql_is_null, na_if = sql_prefix("NULLIF", 2), coalesce = sql_prefix("COALESCE"), as.numeric = sql_cast("NUMERIC"), as.double = sql_cast("NUMERIC"), as.integer = sql_cast("INTEGER"), as.character = sql_cast("TEXT"), as.logical = sql_cast("BOOLEAN"), as.Date = sql_cast("DATE"), as.POSIXct = sql_cast("TIMESTAMP"), # MS SQL - https://docs.microsoft.com/en-us/sql/t-sql/data-types/int-bigint-smallint-and-tinyint-transact-sql # Hive - https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Types#LanguageManualTypes-IntegralTypes(TINYINT,SMALLINT,INT/INTEGER,BIGINT) # Postgres - https://www.postgresql.org/docs/8.4/static/datatype-numeric.html # Impala - https://impala.apache.org/docs/build/html/topics/impala_bigint.html as.integer64 = sql_cast("BIGINT"), c = function(...) c(...), `:` = function(from, to) from:to, between = function(x, left, right) { sql_expr(!!x %BETWEEN% !!left %AND% !!right) }, pmin = sql_aggregate_n("LEAST", "pmin"), pmax = sql_aggregate_n("GREATEST", "pmax"), # Copied onLoad `%>%` = NULL, # lubridate --------------------------------------------------------------- # https://en.wikibooks.org/wiki/SQL_Dialects_Reference/Functions_and_expressions/Date_and_time_functions as_date = sql_cast("DATE"), as_datetime = sql_cast("TIMESTAMP"), today = function() sql_expr(CURRENT_DATE), now = function() sql_expr(CURRENT_TIMESTAMP), # https://modern-sql.com/feature/extract year = function(x) sql_expr(EXTRACT(year %from% !!x)), month = function(x) sql_expr(EXTRACT(month %from% !!x)), day = function(x) sql_expr(EXTRACT(day %from% !!x)), mday = function(x) sql_expr(EXTRACT(day %from% !!x)), yday = sql_not_supported("yday()"), qday = sql_not_supported("qday()"), wday = sql_not_supported("wday()"), hour = function(x) sql_expr(EXTRACT(hour %from% !!x)), minute = function(x) sql_expr(EXTRACT(minute %from% !!x)), second = function(x) sql_expr(EXTRACT(second %from% !!x)), # String functions ------------------------------------------------------ # SQL Syntax reference links: # MySQL https://dev.mysql.com/doc/refman/5.7/en/string-functions.html # Hive: https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF#LanguageManualUDF-StringFunctions # Impala: https://www.cloudera.com/documentation/enterprise/5-9-x/topics/impala_string_functions.html # PostgreSQL: https://www.postgresql.org/docs/9.1/static/functions-string.html # MS SQL: https://docs.microsoft.com/en-us/sql/t-sql/functions/string-functions-transact-sql # Oracle: https://docs.oracle.com/database/121/SQLRF/functions002.htm#SQLRF51180 # base R nchar = sql_prefix("LENGTH", 1), tolower = sql_prefix("LOWER", 1), toupper = sql_prefix("UPPER", 1), trimws = function(x, which = "both") sql_str_trim(x, side = which), paste = sql_paste(" "), paste0 = sql_paste(""), substr = sql_substr("SUBSTR"), substring = sql_substr("SUBSTR"), # stringr functions str_length = sql_prefix("LENGTH", 1), str_to_lower = sql_prefix("LOWER", 1), str_to_upper = sql_prefix("UPPER", 1), str_to_title = sql_prefix("INITCAP", 1), str_trim = sql_str_trim, str_c = sql_paste(""), str_sub = sql_str_sub("SUBSTR"), str_conv = sql_not_supported("str_conv()"), str_count = sql_not_supported("str_count()"), str_detect = sql_not_supported("str_detect()"), str_dup = sql_not_supported("str_dup()"), str_extract = sql_not_supported("str_extract()"), str_extract_all = sql_not_supported("str_extract_all()"), str_flatten = sql_not_supported("str_flatten()"), str_glue = sql_not_supported("str_glue()"), str_glue_data = sql_not_supported("str_glue_data()"), str_interp = sql_not_supported("str_interp()"), str_locate = sql_not_supported("str_locate()"), str_locate_all = sql_not_supported("str_locate_all()"), str_match = sql_not_supported("str_match()"), str_match_all = sql_not_supported("str_match_all()"), str_order = sql_not_supported("str_order()"), str_pad = sql_not_supported("str_pad()"), str_remove = sql_not_supported("str_remove()"), str_remove_all = sql_not_supported("str_remove_all()"), str_replace = sql_not_supported("str_replace()"), str_replace_all = sql_not_supported("str_replace_all()"), str_replace_na = sql_not_supported("str_replace_na()"), str_sort = sql_not_supported("str_sort()"), str_split = sql_not_supported("str_split()"), str_split_fixed = sql_not_supported("str_split_fixed()"), str_squish = sql_not_supported("str_squish()"), str_subset = sql_not_supported("str_subset()"), str_trunc = sql_not_supported("str_trunc()"), str_view = sql_not_supported("str_view()"), str_view_all = sql_not_supported("str_view_all()"), str_which = sql_not_supported("str_which()"), str_wrap = sql_not_supported("str_wrap()") ) base_symbols <- sql_translator( pi = sql("PI()"), `*` = sql("*"), `NULL` = sql("NULL") ) sql_exp <- function(a, x) { a <- as.integer(a) if (identical(a, 1L)) { sql_expr(EXP(!!x)) } else if (identical(a, -1L)) { sql_expr(EXP(-((!!x)))) } else { sql_expr(EXP(!!a * ((!!x)))) } } #' @export #' @rdname sql_variant #' @format NULL base_agg <- sql_translator( # SQL-92 aggregates # http://db.apache.org/derby/docs/10.7/ref/rrefsqlj33923.html n = function() sql("COUNT(*)"), mean = sql_aggregate("AVG", "mean"), var = sql_aggregate("VARIANCE", "var"), sum = sql_aggregate("SUM"), min = sql_aggregate("MIN"), max = sql_aggregate("MAX"), # Ordered set functions quantile = sql_quantile("PERCENTILE_CONT", "ordered"), median = sql_median("PERCENTILE_CONT", "ordered"), # first = sql_prefix("FIRST_VALUE", 1), # last = sql_prefix("LAST_VALUE", 1), # nth = sql_prefix("NTH_VALUE", 2), n_distinct = function(x) { build_sql("COUNT(DISTINCT ", x, ")") } ) #' @export #' @rdname sql_variant #' @format NULL base_win <- sql_translator( # rank functions have a single order argument that overrides the default row_number = win_rank("ROW_NUMBER"), min_rank = win_rank("RANK"), rank = win_rank("RANK"), dense_rank = win_rank("DENSE_RANK"), percent_rank = win_rank("PERCENT_RANK"), cume_dist = win_rank("CUME_DIST"), ntile = function(order_by, n) { win_over( sql_expr(NTILE(!!as.integer(n))), win_current_group(), order_by %||% win_current_order() ) }, # Variants that take more arguments first = function(x, order_by = NULL) { win_over( sql_expr(FIRST_VALUE(!!x)), win_current_group(), order_by %||% win_current_order(), win_current_frame() ) }, last = function(x, order_by = NULL) { win_over( sql_expr(LAST_VALUE(!!x)), win_current_group(), order_by %||% win_current_order(), win_current_frame() ) }, nth = function(x, n, order_by = NULL) { win_over( sql_expr(NTH_VALUE(!!x, !!as.integer(n))), win_current_group(), order_by %||% win_current_order(), win_current_frame() ) }, lead = function(x, n = 1L, default = NA, order_by = NULL) { win_over( sql_expr(LEAD(!!x, !!n, !!default)), win_current_group(), order_by %||% win_current_order(), win_current_frame() ) }, lag = function(x, n = 1L, default = NA, order_by = NULL) { win_over( sql_expr(LAG(!!x, !!as.integer(n), !!default)), win_current_group(), order_by %||% win_current_order(), win_current_frame() ) }, # Recycled aggregate fuctions take single argument, don't need order and # include entire partition in frame. mean = win_aggregate("AVG"), var = win_aggregate("VARIANCE"), sum = win_aggregate("SUM"), min = win_aggregate("MIN"), max = win_aggregate("MAX"), # Ordered set functions quantile = sql_quantile("PERCENTILE_CONT", "ordered", window = TRUE), median = sql_median("PERCENTILE_CONT", "ordered", window = TRUE), # Counts n = function() { win_over(sql("COUNT(*)"), win_current_group()) }, n_distinct = function(x) { win_over(build_sql("COUNT(DISTINCT ", x, ")"), win_current_group()) }, # Cumulative function are like recycled aggregates except that R names # have cum prefix, order_by is inherited and frame goes from -Inf to 0. cummean = win_cumulative("AVG"), cumsum = win_cumulative("SUM"), cummin = win_cumulative("MIN"), cummax = win_cumulative("MAX"), # Manually override other parameters -------------------------------------- order_by = function(order_by, expr) { old <- set_win_current_order(order_by) on.exit(set_win_current_order(old)) expr } ) #' @export #' @rdname sql_variant #' @format NULL base_no_win <- sql_translator( row_number = win_absent("ROW_NUMBER"), min_rank = win_absent("RANK"), rank = win_absent("RANK"), dense_rank = win_absent("DENSE_RANK"), percent_rank = win_absent("PERCENT_RANK"), cume_dist = win_absent("CUME_DIST"), ntile = win_absent("NTILE"), mean = win_absent("AVG"), sd = win_absent("SD"), var = win_absent("VAR"), cov = win_absent("COV"), cor = win_absent("COR"), sum = win_absent("SUM"), min = win_absent("MIN"), max = win_absent("MAX"), median = win_absent("PERCENTILE_CONT"), quantile = win_absent("PERCENTILE_CONT"), n = win_absent("N"), n_distinct = win_absent("N_DISTINCT"), cummean = win_absent("MEAN"), cumsum = win_absent("SUM"), cummin = win_absent("MIN"), cummax = win_absent("MAX"), nth = win_absent("NTH_VALUE"), first = win_absent("FIRST_VALUE"), last = win_absent("LAST_VALUE"), lead = win_absent("LEAD"), lag = win_absent("LAG"), order_by = win_absent("ORDER_BY"), str_flatten = win_absent("STR_FLATTEN"), count = win_absent("COUNT") ) dbplyr/R/explain.R0000644000176200001440000000054214002647450013535 0ustar liggesusers#' @importFrom dplyr show_query #' @export show_query.tbl_lazy <- function(x, ...) { sql <- remote_query(x) cat_line("") cat_line(sql) invisible(x) } #' @importFrom dplyr explain #' @export explain.tbl_sql <- function(x, ...) { force(x) show_query(x) cat_line() cat_line("") cat_line(remote_query_plan(x)) invisible(x) } dbplyr/R/translate-sql-helpers.R0000644000176200001440000001602014002647450016325 0ustar liggesusers#' Create an sql translator #' #' When creating a package that maps to a new SQL based src, you'll often #' want to provide some additional mappings from common R commands to the #' commands that your tbl provides. These three functions make that #' easy. #' #' @section Helper functions: #' #' `sql_infix()` and `sql_prefix()` create default SQL infix and prefix #' functions given the name of the SQL function. They don't perform any input #' checking, but do correctly escape their input, and are useful for #' quickly providing default wrappers for a new SQL variant. #' #' @keywords internal #' @seealso [win_over()] for helper functions for window functions. #' @param scalar,aggregate,window The three families of functions than an #' SQL variant can supply. #' @param ...,.funs named functions, used to add custom converters from standard #' R functions to sql functions. Specify individually in `...`, or #' provide a list of `.funs` #' @param .parent the sql variant that this variant should inherit from. #' Defaults to `base_agg` which provides a standard set of #' mappings for the most common operators and functions. #' @param f the name of the sql function as a string #' @param f_r the name of the r function being translated as a string #' @param n for `sql_infix()`, an optional number of arguments to expect. #' Will signal error if not correct. #' @seealso [sql()] for an example of a more customised sql #' conversion function. #' @export #' @examples #' # An example of adding some mappings for the statistical functions that #' # postgresql provides: http://bit.ly/K5EdTn #' #' postgres_agg <- sql_translator(.parent = base_agg, #' cor = sql_aggregate_2("CORR"), #' cov = sql_aggregate_2("COVAR_SAMP"), #' sd = sql_aggregate("STDDEV_SAMP", "sd"), #' var = sql_aggregate("VAR_SAMP", "var") #' ) #' #' # Next we have to simulate a connection that uses this variant #' con <- simulate_dbi("TestCon") #' sql_translation.TestCon <- function(x) { #' sql_variant( #' base_scalar, #' postgres_agg, #' base_no_win #' ) #' } #' #' translate_sql(cor(x, y), con = con, window = FALSE) #' translate_sql(sd(income / years), con = con, window = FALSE) #' #' # Any functions not explicitly listed in the converter will be translated #' # to sql as is, so you don't need to convert all functions. #' translate_sql(regr_intercept(y, x), con = con) sql_variant <- function(scalar = sql_translator(), aggregate = sql_translator(), window = sql_translator()) { stopifnot(is.environment(scalar)) stopifnot(is.environment(aggregate)) stopifnot(is.environment(window)) # Need to check that every function in aggregate also occurs in window missing <- setdiff(ls(aggregate), ls(window)) if (length(missing) > 0) { warn(paste0( "Translator is missing window variants of the following aggregate functions:\n", paste0("* ", missing, "\n", collapse = "") )) } # An ensure that every window function is flagged in aggregate context missing <- setdiff(ls(window), ls(aggregate)) missing_funs <- lapply(missing, sql_aggregate_win) env_bind(aggregate, !!!set_names(missing_funs, missing)) structure( list(scalar = scalar, aggregate = aggregate, window = window), class = "sql_variant" ) } is.sql_variant <- function(x) inherits(x, "sql_variant") #' @export print.sql_variant <- function(x, ...) { wrap_ls <- function(x, ...) { vars <- sort(ls(envir = x)) wrapped <- strwrap(paste0(vars, collapse = ", "), ...) if (identical(wrapped, "")) return() paste0(wrapped, "\n", collapse = "") } cat("\n") cat(wrap_ls( x$scalar, prefix = "scalar: " )) cat(wrap_ls( x$aggregate, prefix = "aggregate: " )) cat(wrap_ls( x$window, prefix = "window: " )) } #' @export names.sql_variant <- function(x) { c(ls(envir = x$scalar), ls(envir = x$aggregate), ls(envir = x$window)) } #' @export #' @rdname sql_variant sql_translator <- function(..., .funs = list(), .parent = new.env(parent = emptyenv())) { funs <- c(list2(...), .funs) if (length(funs) == 0) return(.parent) list2env(funs, copy_env(.parent)) } copy_env <- function(from, to = NULL, parent = parent.env(from)) { list2env(as.list(from), envir = to, parent = parent) } #' @rdname sql_variant #' @param pad If `TRUE`, the default, pad the infix operator with spaces. #' @export sql_infix <- function(f, pad = TRUE) { assert_that(is_string(f)) if (pad) { function(x, y) { build_sql(x, " ", sql(f), " ", y) } } else { function(x, y) { build_sql(x, sql(f), y) } } } #' @rdname sql_variant #' @export sql_prefix <- function(f, n = NULL) { assert_that(is_string(f)) function(...) { args <- list(...) if (!is.null(n) && length(args) != n) { stop( "Invalid number of args to SQL ", f, ". Expecting ", n, call. = FALSE ) } if (any(names2(args) != "")) { warning("Named arguments ignored for SQL ", f, call. = FALSE) } build_sql(sql(f), args) } } #' @rdname sql_variant #' @export sql_aggregate <- function(f, f_r = f) { assert_that(is_string(f)) warned <- FALSE function(x, na.rm = FALSE) { warned <<- check_na_rm(f_r, na.rm, warned) build_sql(sql(f), list(x)) } } #' @rdname sql_variant #' @export sql_aggregate_2 <- function(f) { assert_that(is_string(f)) function(x, y) { build_sql(sql(f), list(x, y)) } } #' @rdname sql_variant #' @export sql_aggregate_n <- function(f, f_r = f) { assert_that(is_string(f)) warned <- FALSE function(..., na.rm = FALSE) { warned <<- check_na_rm(f_r, na.rm, warned) build_sql(sql(f), list(...)) } } sql_aggregate_win <- function(f) { force(f) function(...) { stop( "`", f, "()` is only available in a windowed (`mutate()`) context", call. = FALSE ) } } check_na_rm <- function(f, na.rm, warned) { if (warned || identical(na.rm, TRUE)) { return(warned) } warning( "Missing values are always removed in SQL.\n", "Use `", f, "(x, na.rm = TRUE)` to silence this warning\n", "This warning is displayed only once per session.", call. = FALSE ) TRUE } #' @rdname sql_variant #' @export sql_not_supported <- function(f) { assert_that(is_string(f)) function(...) { stop(f, " is not available in this SQL variant", call. = FALSE) } } #' @rdname sql_variant #' @export sql_cast <- function(type) { type <- sql(type) function(x) { sql_expr(cast(!!x %as% !!type)) } } #' @rdname sql_variant #' @export sql_try_cast <- function(type) { type <- sql(type) function(x) { sql_expr(try_cast(!!x %as% !!type)) # try_cast available in MSSQL 2012+ } } #' @rdname sql_variant #' @export sql_log <- function() { function(x, base = exp(1)){ if (isTRUE(all.equal(base, exp(1)))) { sql_expr(ln(!!x)) } else { sql_expr(log(!!x) / log(!!base)) } } } #' @rdname sql_variant #' @export sql_cot <- function(){ function(x){ sql_expr(1L / tan(!!x)) } } globalVariables(c("%as%", "cast", "ln", "try_cast")) dbplyr/R/verb-slice.R0000644000176200001440000001212014004012136014110 0ustar liggesusers#' Subset rows using their positions #' #' @description #' These are methods for the dplyr generics [slice_min()], [slice_max()], and #' [slice_sample()]. They are translated to SQL using [filter()] and #' window functions (`ROWNUMBER`, `MIN_RANK`, or `CUME_DIST` depending on #' arguments). `slice()`, `slice_head()`, and `slice_tail()` are not supported #' since database tables have no intrinsic order. #' #' If data is grouped, the operation will be performed on each group so that #' (e.g.) `slice_min(db, x, n = 3)` will select the three rows with the smallest #' value of `x` in each group. #' #' @inheritParams arrange.tbl_lazy #' @param ... Not used. #' @param n,prop Provide either `n`, the number of rows, or `prop`, the #' proportion of rows to select. If neither are supplied, `n = 1` will be #' used. #' #' If `n` is greater than the number of rows in the group (or `prop` > 1), #' the result will be silently truncated to the group size. If the proportion #' of a group size is not an integer, it is rounded down. #' @param order_by Variable or function of variables to order by. #' @param with_ties Should ties be kept together? The default, `TRUE`, may #' return more rows than you request. Use FALSE to ignore ties, and return #' the first n rows. #' @param weight_by,replace Not supported for database backends. #' @name dbplyr-slice #' @aliases NULL #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' db <- memdb_frame(x = 1:3, y = c(1, 1, 2)) #' db %>% slice_min(x) %>% show_query() #' db %>% slice_max(x) %>% show_query() #' db %>% slice_sample() %>% show_query() #' #' db %>% group_by(y) %>% slice_min(x) %>% show_query() #' #' # By default, ties are includes so you may get more rows #' # than you expect #' db %>% slice_min(y, n = 1) #' db %>% slice_min(y, n = 1, with_ties = FALSE) #' #' # Non-integer group sizes are rounded down #' db %>% slice_min(x, prop = 0.5) NULL #' @importFrom dplyr slice #' @export slice.tbl_lazy <- function(.data, ...) { abort("slice() is not supported on database backends") } #' @importFrom dplyr slice_head #' @export slice_head.tbl_lazy <- function(.data, ..., n, prop) { abort(c( "slice_head() is not supported on database backends", i = "Please use slice_min() instead" )) } #' @importFrom dplyr slice_tail #' @export slice_tail.tbl_lazy <- function(.data, ..., n, prop) { abort(c( "slice_tail() is not supported on database backends", i = "Please use slice_max() instead" )) } #' @rdname dbplyr-slice #' @importFrom dplyr slice_min #' @export slice_min.tbl_lazy <- function(.data, order_by, ..., n, prop, with_ties = TRUE) { if (missing(order_by)) { abort("Argument `order_by` is missing, with no default.") } size <- check_slice_size(n, prop) slice_by(.data, {{order_by}}, size, with_ties = with_ties) } #' @rdname dbplyr-slice #' @importFrom dplyr slice_max #' @export slice_max.tbl_lazy <- function(.data, order_by, ..., n, prop, with_ties = TRUE) { if (missing(order_by)) { abort("Argument `order_by` is missing, with no default.") } size <- check_slice_size(n, prop) slice_by(.data, desc({{order_by}}), size, with_ties = with_ties) } #' @rdname dbplyr-slice #' @importFrom dplyr slice_sample #' @export slice_sample.tbl_lazy <- function(.data, ..., n, prop, weight_by = NULL, replace = FALSE) { size <- check_slice_size(n, prop) weight_by <- enquo(weight_by) if (size$type == "prop") { abort("Sampling by `prop` is not supported on database backends") } if (!quo_is_null(weight_by)) { abort("Weighted resampling is not supported on database backends") } if (replace) { abort("Sampling with replacement is not supported on database backends") } slice_by(.data, random(), size, with_ties = FALSE) } slice_by <- function(.data, order_by, size, with_ties = FALSE) { old_frame <- op_sort(.data) if (with_ties) { window_fun <- switch(size$type, n = expr(min_rank() <= !!size$n), prop = expr(cume_dist() <= !!size$prop) ) } else { window_fun <- switch(size$type, n = expr(row_number() <= !!size$n), prop = abort("Can only use `prop` when `with_ties = TRUE`") ) } .data %>% window_order({{order_by}}) %>% filter(!!window_fun) %>% window_order(!!!old_frame) } # helpers ----------------------------------------------------------------- check_slice_size <- function(n, prop) { if (missing(n) && missing(prop)) { list(type = "n", n = 1L) } else if (!missing(n) && missing(prop)) { if (!is.numeric(n) || length(n) != 1) { abort("`n` must be a single number.") } if (is.na(n) || n < 0) { abort("`n` must be a non-missing positive number.") } list(type = "n", n = as.integer(n)) } else if (!missing(prop) && missing(n)) { if (!is.numeric(prop) || length(prop) != 1) { abort("`prop` must be a single number") } if (is.na(prop) || prop < 0) { abort("`prop` must be a non-missing positive number.") } list(type = "prop", prop = prop) } else { abort("Must supply exactly one of `n` and `prop` arguments.") } } globalVariables(c("min_rank", "cume_dist", "row_number", "desc", "random")) dbplyr/R/verb-expand.R0000644000176200001440000001020214004012136014267 0ustar liggesusers#' Expand SQL tables to include all possible combinations of values #' #' @description #' This is a method for the [tidyr::expand] generics. It doesn't sort the #' result explicitly, so the order might be different to what `expand()` #' returns for data frames. #' #' @param data A lazy data frame backed by a database query. #' @param ... Specification of columns to expand. See [tidyr::expand] for #' more details. #' @inheritParams tibble::as_tibble #' @inherit arrange.tbl_lazy return #' @examples #' if (require("tidyr", quietly = TRUE)) { #' fruits <- memdb_frame( #' type = c("apple", "orange", "apple", "orange", "orange", "orange"), #' year = c(2010, 2010, 2012, 2010, 2010, 2012), #' size = c("XS", "S", "M", "S", "S", "M"), #' weights = rnorm(6) #' ) #' #' # All possible combinations --------------------------------------- #' fruits %>% expand(type) #' fruits %>% expand(type, size) #' #' # Only combinations that already appear in the data --------------- #' fruits %>% expand(nesting(type, size)) #' } expand.tbl_lazy <- function(data, ..., .name_repair = "check_unique") { dots <- purrr::discard(quos(...), quo_is_null) if (is_empty(dots)) { abort("Must supply variables in `...`") } extract_dot_vars <- function(.x) { # ugly hack to deal with `nesting()` if (quo_is_call(.x, name = "nesting")) { x_expr <- quo_get_expr(.x) call_args(x_expr) } else { list(quo_get_expr(.x)) } } distinct_tbl_vars <- purrr::map(dots, extract_dot_vars) # now that `nesting()` has been unpacked resolve name conflicts out_names <- names(exprs_auto_name(purrr::flatten(distinct_tbl_vars))) out_names_repaired <- vctrs::vec_as_names(out_names, repair = .name_repair) ns <- lengths(distinct_tbl_vars) indices <- vctrs::vec_rep_each(seq_along(distinct_tbl_vars), ns) out_names_list <- vctrs::vec_split(out_names_repaired, indices)$val distinct_tables <- purrr::map2( distinct_tbl_vars, out_names_list, ~ { args <- set_names(.x, .y) distinct(data, !!!args) } ) purrr::reduce(distinct_tables, left_join, by = group_vars(data)) } #' Complete a SQL table with missing combinations of data #' #' Turns implicit missing values into explicit missing values. This is a method #' for the [tidyr::complete()] generic. #' #' @inheritParams expand.tbl_lazy #' @param fill A named list that for each variable supplies a single value to #' use instead of NA for missing combinations. #' #' @inherit arrange.tbl_lazy return #' #' @examples #' if (require("tidyr", quietly = TRUE)) { #' df <- memdb_frame( #' group = c(1:2, 1), #' item_id = c(1:2, 2), #' item_name = c("a", "b", "b"), #' value1 = 1:3, #' value2 = 4:6 #' ) #' #' df %>% complete(group, nesting(item_id, item_name)) #' #' # You can also choose to fill in missing values #' df %>% complete(group, nesting(item_id, item_name), fill = list(value1 = 0)) #' } complete.tbl_lazy <- function(data, ..., fill = list()) { full <- tidyr::expand(data, ...) if (is_empty(full)) { return(data) } full <- full_join(full, data, by = colnames(full)) tidyr::replace_na(full, replace = fill) } #' Replace NAs with specified values #' #' This is a method for the [tidyr::replace_na()] generic. #' #' @param data A pair of lazy data frame backed by database queries. #' @param replace A named list of values, with one value for each column that #' has NA values to be replaced. #' @param ... Unused; included for compatibility with generic. #' #' @inherit arrange.tbl_lazy return #' #' @examples #' if (require("tidyr", quietly = TRUE)) { #' df <- memdb_frame(x = c(1, 2, NA), y = c("a", NA, "b")) #' df %>% replace_na(list(x = 0, y = "unknown")) #' } replace_na.tbl_lazy <- function(data, replace = list(), ...) { stopifnot(is_list(replace)) stopifnot(is_empty(replace) || is_named(replace)) replace <- replace[names(replace) %in% colnames(data)] if (is_empty(replace)) { return(data) } coalesce_expr <- purrr::imap( replace, function(value, col) { quo(coalesce(!!sym(col), !!value)) } ) mutate(data, !!!coalesce_expr) } globalVariables(c("coalesce", "expand", "replace_na")) dbplyr/R/query-join.R0000644000176200001440000000557714002647450014214 0ustar liggesusers#' @export #' @rdname sql_build join_query <- function(x, y, vars, type = "inner", by = NULL, suffix = c(".x", ".y"), na_matches = FALSE) { structure( list( x = x, y = y, vars = vars, type = type, by = by, na_matches = na_matches ), class = c("join_query", "query") ) } #' @export print.join_query <- function(x, ...) { cat_line("") cat_line("By:") cat_line(indent(paste0(x$by$x, "-", x$by$y))) cat_line("X:") cat_line(indent_print(sql_build(x$x))) cat_line("Y:") cat_line(indent_print(sql_build(x$y))) } #' @export sql_render.join_query <- function(query, con = NULL, ..., subquery = FALSE) { from_x <- dbplyr_sql_subquery( con, sql_render(query$x, con, ..., subquery = TRUE), name = "LHS" ) from_y <- dbplyr_sql_subquery( con, sql_render(query$y, con, ..., subquery = TRUE), name = "RHS" ) dbplyr_query_join(con, from_x, from_y, vars = query$vars, type = query$type, by = query$by, na_matches = query$na_matches ) } # SQL generation ---------------------------------------------------------- sql_join_vars <- function(con, vars) { sql_vector( mapply( FUN = sql_join_var, alias = vars$alias, x = vars$x, y = vars$y, MoreArgs = list(con = con, all_x = vars$all_x, all_y = vars$all_y), SIMPLIFY = FALSE, USE.NAMES = TRUE ), parens = FALSE, collapse = ", ", con = con ) } sql_join_var <- function(con, alias, x, y, all_x, all_y) { if (!is.na(x) && !is.na(y)) { sql_expr( COALESCE( !!sql_table_prefix(con, x, table = "LHS"), !!sql_table_prefix(con, y, table = "RHS") ), con = con ) } else if (!is.na(x)) { sql_table_prefix(con, x, table = if (x %in% all_y) "LHS") } else if (!is.na(y)) { sql_table_prefix(con, y, table = if (y %in% all_x) "RHS") } else { stop("No source for join column ", alias, call. = FALSE) } } sql_join_tbls <- function(con, by, na_matches = "never") { na_matches <- arg_match(na_matches, c("na", "never")) on <- NULL if (na_matches == "na" || length(by$x) + length(by$y) > 0) { lhs <- sql_table_prefix(con, by$x, "LHS") rhs <- sql_table_prefix(con, by$y, "RHS") if (na_matches == "na") { compare <- purrr::map_chr(seq_along(lhs), function(i) { sql_expr_matches(sql(lhs[[i]]), sql(rhs[[i]]), con = con) }) } else { compare <- paste0(lhs, " = ", rhs) } on <- sql_vector(compare, collapse = " AND ", parens = TRUE, con = con) } else if (length(by$on) > 0) { on <- build_sql("(", by$on, ")", con = con) } on } sql_table_prefix <- function(con, var, table = NULL) { var <- sql_escape_ident(con, var) if (!is.null(table)) { table <- sql_escape_ident(con, table) sql(paste0(table, ".", var)) } else { var } } utils::globalVariables("COALESCE") dbplyr/R/verb-filter.R0000644000176200001440000000315114002647450014315 0ustar liggesusers#' Subset rows using column values #' #' This is a method for the dplyr [filter()] generic. It generates the #' `WHERE` clause of the SQL query. #' #' @inheritParams arrange.tbl_lazy #' @inheritParams dplyr::filter #' @param .preserve Not supported by this method. #' @inherit arrange.tbl_lazy return #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' db <- memdb_frame(x = c(2, NA, 5, NA, 10), y = 1:5) #' db %>% filter(x < 5) %>% show_query() #' db %>% filter(is.na(x)) %>% show_query() # registered onLoad #' @importFrom dplyr filter filter.tbl_lazy <- function(.data, ..., .preserve = FALSE) { if (!identical(.preserve, FALSE)) { stop("`.preserve` is not supported on database backends", call. = FALSE) } dots <- quos(...) dots <- partial_eval_dots(dots, vars = op_vars(.data)) add_op_single("filter", .data, dots = dots) } #' @export sql_build.op_filter <- function(op, con, ...) { vars <- op_vars(op$x) if (!uses_window_fun(op$dots, con)) { where_sql <- translate_sql_(op$dots, con, context = list(clause = "WHERE")) select_query( sql_build(op$x, con), where = where_sql ) } else { # Do partial evaluation, then extract out window functions where <- translate_window_where_all(op$dots, ls(dbplyr_sql_translation(con)$window)) # Convert where$expr back to a lazy dots object, and then # create mutate operation mutated <- sql_build(new_op_select(op$x, carry_over(vars, where$comp)), con = con) where_sql <- translate_sql_(where$expr, con = con, context = list(clause = "WHERE")) select_query(mutated, select = ident(vars), where = where_sql) } } dbplyr/R/reexport.R0000644000176200001440000000003314002647450013740 0ustar liggesusers#' @export magrittr::`%>%` dbplyr/R/query.R0000644000176200001440000000014614002647450013242 0ustar liggesusers#' @export sql_optimise.query <- function(x, con = NULL, ...) { # Default to no optimisation x } dbplyr/R/simulate.R0000644000176200001440000000230214002647450013714 0ustar liggesusers#' Simulate database connections #' #' These functions generate S3 objects that have been designed to simulate #' the action of a database connection, without actually having the database #' available. Obviously, this simulation can only be incomplete, but most #' importantly it allows us to simulate SQL generation for any database without #' actually connecting to it. #' #' Simulated SQL always quotes identifies with `` `x` ``, and strings with #' `'x'`. #' #' @keywords internal #' @export simulate_dbi <- function(class = character(), ...) { structure( list(), ..., class = c(class, "TestConnection", "DBIConnection") ) } #' @export dbplyr_edition.TestConnection <- function(con) 2L sql_escape_ident <- function(con, x) { UseMethod("sql_escape_ident") } #' @export sql_escape_ident.DBIConnection <- function(con, x) { dbQuoteIdentifier(con, x) } #' @export sql_escape_ident.TestConnection <- function(con, x) { sql_quote(x, "`") } sql_escape_string <- function(con, x) { UseMethod("sql_escape_string") } #' @export sql_escape_string.DBIConnection <- function(con, x) { dbQuoteString(con, x) } #' @export sql_escape_string.TestConnection <- function(con, x) { sql_quote(x, "'") } dbplyr/R/escape.R0000644000176200001440000001320114002647450013331 0ustar liggesusers#' Escape/quote a string. #' #' `escape()` requires you to provide a database connection to control the #' details of escaping. `escape_ansi()` uses the SQL 92 ANSI standard. #' #' @param x An object to escape. Existing sql vectors will be left as is, #' character vectors are escaped with single quotes, numeric vectors have #' trailing `.0` added if they're whole numbers, identifiers are #' escaped with double quotes. #' @param parens,collapse Controls behaviour when multiple values are supplied. #' `parens` should be a logical flag, or if `NA`, will wrap in #' parens if length > 1. #' #' Default behaviour: lists are always wrapped in parens and separated by #' commas, identifiers are separated by commas and never wrapped, #' atomic vectors are separated by spaces and wrapped in parens if needed. #' @param con Database connection. #' @rdname escape #' @export #' @examples #' # Doubles vs. integers #' escape_ansi(1:5) #' escape_ansi(c(1, 5.4)) #' #' # String vs known sql vs. sql identifier #' escape_ansi("X") #' escape_ansi(sql("X")) #' escape_ansi(ident("X")) #' #' # Escaping is idempotent #' escape_ansi("X") #' escape_ansi(escape_ansi("X")) #' escape_ansi(escape_ansi(escape_ansi("X"))) escape <- function(x, parens = NA, collapse = " ", con = NULL) { if (is.null(con)) { stop("`con` must not be NULL", call. = FALSE) } UseMethod("escape") } #' @export #' @rdname escape escape_ansi <- function(x, parens = NA, collapse = "") { escape(x, parens = parens, collapse = collapse, con = simulate_dbi()) } #' @export escape.ident <- function(x, parens = FALSE, collapse = ", ", con = NULL) { y <- sql_escape_ident(con, x) sql_vector(names_to_as(y, names2(x), con = con), parens, collapse, con = con) } #' @export escape.logical <- function(x, parens = NA, collapse = ", ", con = NULL) { sql_vector(sql_escape_logical(con, x), parens, collapse, con = con) } #' @export escape.factor <- function(x, parens = NA, collapse = ", ", con = NULL) { x <- as.character(x) escape.character(x, parens = parens, collapse = collapse, con = con) } #' @export escape.Date <- function(x, parens = NA, collapse = ", ", con = NULL) { sql_vector(sql_escape_date(con, x), parens, collapse, con = con) } #' @export escape.POSIXt <- function(x, parens = NA, collapse = ", ", con = NULL) { sql_vector(sql_escape_datetime(con, x), parens, collapse, con = con) } #' @export escape.character <- function(x, parens = NA, collapse = ", ", con = NULL) { sql_vector(sql_escape_string(con, x), parens, collapse, con = con) } #' @export escape.double <- function(x, parens = NA, collapse = ", ", con = NULL) { out <- ifelse(is.wholenumber(x), sprintf("%.1f", x), as.character(x)) # Special values out[is.na(x)] <- "NULL" inf <- is.infinite(x) out[inf & x > 0] <- "'Infinity'" out[inf & x < 0] <- "'-Infinity'" sql_vector(out, parens, collapse, con = con) } #' @export escape.integer <- function(x, parens = NA, collapse = ", ", con = NULL) { x[is.na(x)] <- "NULL" sql_vector(x, parens, collapse, con = con) } #' @export escape.integer64 <- function(x, parens = NA, collapse = ", ", con = NULL) { x <- as.character(x) x[is.na(x)] <- "NULL" sql_vector(x, parens, collapse, con = con) } #' @export escape.blob <- function(x, parens = NA, collapse = ", ", con = NULL) { pieces <- vapply(x, sql_escape_raw, character(1), con = con) sql_vector(pieces, isTRUE(parens) || length(pieces) > 1, collapse, con = con) } #' @export escape.NULL <- function(x, parens = NA, collapse = " ", con = NULL) { sql("NULL") } #' @export escape.sql <- function(x, parens = NULL, collapse = NULL, con = NULL) { sql_vector(x, isTRUE(parens), collapse, con = con) } #' @export escape.list <- function(x, parens = TRUE, collapse = ", ", con = NULL) { pieces <- vapply(x, escape, character(1), con = con) sql_vector(pieces, parens, collapse, con = con) } #' @export escape.data.frame <- function(x, parens = TRUE, collapse = ", ", con = NULL) { error_embed("a data.frame", "df$x") } #' @export escape.reactivevalues <- function(x, parens = TRUE, collapse = ", ", con = NULL) { error_embed("shiny inputs", "inputs$x") } # Also used in default_ops() for reactives error_embed <- function(type, expr) { abort(c( glue("Cannot translate {type} to SQL."), glue("Force evaluation in R with (e.g.) `!!{expr}` or `local({expr})`") )) } #' @export #' @rdname escape sql_vector <- function(x, parens = NA, collapse = " ", con = NULL) { if (is.null(con)) { stop("`con` must not be NULL", call. = FALSE) } if (length(x) == 0) { if (!is.null(collapse)) { return(if (isTRUE(parens)) sql("()") else sql("")) } else { return(sql()) } } if (is.na(parens)) { parens <- length(x) > 1L } x <- names_to_as(x, con = con) x <- paste(x, collapse = collapse) if (parens) x <- paste0("(", x, ")") sql(x) } names_to_as <- function(x, names = names2(x), con = NULL) { if (length(x) == 0) { return(character()) } names_esc <- sql_escape_ident(con, names) as <- ifelse(names == "" | names_esc == x, "", paste0(" AS ", names_esc)) paste0(x, as) } #' Helper function for quoting sql elements. #' #' If the quote character is present in the string, it will be doubled. #' `NA`s will be replaced with NULL. #' #' @param x Character vector to escape. #' @param quote Single quoting character. #' @export #' @keywords internal #' @examples #' sql_quote("abc", "'") #' sql_quote("I've had a good day", "'") #' sql_quote(c("abc", NA), "'") sql_quote <- function(x, quote) { if (length(x) == 0) { return(x) } y <- gsub(quote, paste0(quote, quote), x, fixed = TRUE) y <- paste0(quote, y, quote) y[is.na(x)] <- "NULL" names(y) <- names(x) y } dbplyr/R/verb-group_by.R0000644000176200001440000000452514031447261014664 0ustar liggesusers#' Group by one or more variables #' #' This is a method for the dplyr [group_by()] generic. It is translated to #' the `GROUP BY` clause of the SQL query when used with #' [`summarise()`][summarise.tbl_lazy] and to the `PARTITION BY` clause of #' window functions when used with [`mutate()`][mutate.tbl_lazy]. #' #' @inheritParams arrange.tbl_lazy #' @inheritParams dplyr::group_by #' @param .drop Not supported by this method. #' @param add Deprecated. Please use `.add` instead. #' @export #' @importFrom dplyr group_by #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' db <- memdb_frame(g = c(1, 1, 1, 2, 2), x = c(4, 3, 6, 9, 2)) #' db %>% #' group_by(g) %>% #' summarise(n()) %>% #' show_query() #' #' db %>% #' group_by(g) %>% #' mutate(x2 = x / sum(x, na.rm = TRUE)) %>% #' show_query() group_by.tbl_lazy <- function(.data, ..., .add = FALSE, add = NULL, .drop = TRUE) { dots <- quos(...) dots <- partial_eval_dots(dots, vars = op_vars(.data)) if (!missing(add)) { lifecycle::deprecate_warn("1.0.0", "dplyr::group_by(add = )", "group_by(.add = )") .add <- add } if (!identical(.drop, TRUE)) { stop("`.drop` is not supported with database backends", call. = FALSE) } if (length(dots) == 0) { if (.add) { return(.data) } else { return(dplyr::ungroup(.data)) } } if (".add" %in% names(formals("group_by"))) { groups <- dplyr::group_by_prepare(.data, !!!dots, .add = .add) } else { groups <- dplyr::group_by_prepare(.data, !!!dots, add = .add) } names <- purrr::map_chr(groups$groups, as_string) add_op_single("group_by", groups$data, dots = set_names(groups$groups, names), args = list(add = FALSE) ) } #' @export op_desc.op_group_by <- function(x, ...) { op_desc(x$x, ...) } #' @export op_grps.op_group_by <- function(op) { if (isTRUE(op$args$add)) { union(op_grps(op$x), names(op$dots)) } else { names(op$dots) } } #' @export sql_build.op_group_by <- function(op, con, ...) { sql_build(op$x, con, ...) } # ungroup ----------------------------------------------------------------- #' @importFrom dplyr ungroup #' @export ungroup.tbl_lazy <- function(x, ...) { add_op_single("ungroup", x) } #' @export op_grps.op_ungroup <- function(op) { character() } #' @export sql_build.op_ungroup <- function(op, con, ...) { sql_build(op$x, con, ...) } dbplyr/R/ident.R0000644000176200001440000000173614002647450013206 0ustar liggesusers#' Flag a character vector as SQL identifiers #' #' `ident()` takes unquoted strings and flags them as identifiers. #' `ident_q()` assumes its input has already been quoted, and ensures #' it does not get quoted again. This is currently used only for #' for `schema.table`. #' #' @param ... A character vector, or name-value pairs #' @param x An object #' @export #' @examples #' # SQL92 quotes strings with ' #' escape_ansi("x") #' #' # And identifiers with " #' ident("x") #' escape_ansi(ident("x")) #' #' # You can supply multiple inputs #' ident(a = "x", b = "y") #' ident_q(a = "x", b = "y") ident <- function(...) { x <- c_character(...) structure(x, class = c("ident", "character")) } #' @export print.ident <- function(x, ...) cat(format(x, ...), sep = "\n") #' @export format.ident <- function(x, ...) { if (length(x) == 0) { paste0(" [empty]") } else { paste0(" ", x) } } #' @rdname ident #' @export is.ident <- function(x) inherits(x, "ident") dbplyr/R/verb-pivot-longer.R0000644000176200001440000001645014004012136015450 0ustar liggesusers#' Pivot data from wide to long #' #' @description #' `pivot_longer()` "lengthens" data, increasing the number of rows and #' decreasing the number of columns. The inverse transformation is #' `tidyr::pivot_wider()] #' #' Learn more in `vignette("pivot", "tidyr")`. #' #' While most functionality is identical there are some differences to #' `pivot_longer()` on local data frames: #' * the output is sorted differently/not explicitly, #' * the coercion of mixed column types is left to the database, #' * `values_ptypes` NOT supported. #' #' Note that `build_longer_spec()` and `pivot_longer_spec()` do not work with #' remote tables. #' #' @details #' The SQL translation basically works as follows: #' #' 1. split the specification by its key columns i.e. by variables crammed #' into the column names. #' 2. for each part in the splitted specification `transmute()` `data` into the #' following columns #' * id columns i.e. columns that are not pivotted #' * key columns #' * value columns i.e. columns that are pivotted #' 3. combine all the parts with `union_all()` #' #' @param data A data frame to pivot. #' @param cols Columns to pivot into longer format. #' @param names_to A string specifying the name of the column to create #' from the data stored in the column names of `data`. #' @param names_prefix A regular expression used to remove matching text #' from the start of each variable name. #' @param names_sep,names_pattern If `names_to` contains multiple values, #' these arguments control how the column name is broken up. #' @param names_repair What happens if the output has invalid column names? #' @param values_to A string specifying the name of the column to create #' from the data stored in cell values. If `names_to` is a character #' containing the special `.value` sentinel, this value will be ignored, #' and the name of the value column will be derived from part of the #' existing column names. #' @param values_drop_na If `TRUE`, will drop rows that contain only `NA`s #' in the `value_to` column. #' @param names_transform,values_transform A list of column name-function pairs. #' @param names_ptypes A list of column name-prototype pairs. #' @param values_ptypes Not supported. #' @param ... Additional arguments passed on to methods. #' @examples #' # See vignette("pivot") for examples and explanation #' #' # Simplest case where column names are character data #' if (require("tidyr", quietly = TRUE)) { #' memdb_frame( #' id = c("a", "b"), #' x = 1:2, #' y = 3:4 #' ) %>% #' pivot_longer(-id) #' } pivot_longer.tbl_lazy <- function(data, cols, names_to = "name", names_prefix = NULL, names_sep = NULL, names_pattern = NULL, names_ptypes = list(), names_transform = list(), names_repair = "check_unique", values_to = "value", values_drop_na = FALSE, values_ptypes, values_transform = list(), ...) { if (!is_missing(values_ptypes)) { abort("The `values_ptypes` argument is not supported for remote back-ends") } ellipsis::check_dots_empty() cols <- enquo(cols) spec <- tidyr::build_longer_spec(simulate_vars(data), !!cols, names_to = names_to, values_to = values_to, names_prefix = names_prefix, names_sep = names_sep, names_pattern = names_pattern, names_ptypes = names_ptypes, names_transform = names_transform ) dbplyr_pivot_longer_spec(data, spec, names_repair = names_repair, values_drop_na = values_drop_na, values_transform = values_transform ) } dbplyr_pivot_longer_spec <- function(data, spec, names_repair = "check_unique", values_drop_na = FALSE, values_transform = list()) { spec <- check_spec(spec) # .seq col needed if different input columns are mapped to the same output # column spec <- deduplicate_spec(spec, data) id_cols <- syms(setdiff(colnames(data), spec$.name)) spec_split <- vctrs::vec_split(spec, spec[, -(1:2)]) data_long_list <- purrr::map( vctrs::vec_seq_along(spec_split), function(idx) { row <- spec_split$val[[idx]][, 1:2] keys <- spec_split$key[idx, ] keys$.seq <- NULL measure_cols_exprs <- get_measure_column_exprs( row[[".name"]], row[[".value"]], values_transform ) transmute( data, !!!id_cols, !!!keys, !!!measure_cols_exprs ) } ) data_long <- purrr::reduce(data_long_list, union_all) if (values_drop_na) { value_cols <- unique(spec$.value) data_long <- dplyr::filter_at( data_long, value_cols, dplyr::all_vars(!is.na(.)) ) } data_long } get_measure_column_exprs <- function(name, value, values_transform) { # idea copied from `partial_eval_across` measure_funs <- syms(purrr::map_chr(values_transform, find_fun)) measure_cols <- set_names(syms(name), value) purrr::imap( measure_cols, ~ { f_trans <- measure_funs[[.y]] if (is_null(f_trans)) { .x } else { expr((!!f_trans)(!!.x)) } } ) } # The following is copy-pasted from `tidyr` # `check_spec()` can be removed once it is exported by `tidyr` # see https://github.com/tidyverse/tidyr/issues/1087 # nocov start check_spec <- function(spec) { # COPIED FROM tidyr # Eventually should just be vec_assert() on partial_frame() # Waiting for https://github.com/r-lib/vctrs/issues/198 if (!is.data.frame(spec)) { stop("`spec` must be a data frame", call. = FALSE) } if (!has_name(spec, ".name") || !has_name(spec, ".value")) { stop("`spec` must have `.name` and `.value` columns", call. = FALSE) } # Ensure .name and .value come first vars <- union(c(".name", ".value"), names(spec)) spec[vars] } # Ensure that there's a one-to-one match from spec to data by adding # a special .seq variable which is automatically removed after pivotting. deduplicate_spec <- function(spec, df) { # COPIED FROM tidyr # Ensure each .name has a unique output identifier key <- spec[setdiff(names(spec), ".name")] if (vctrs::vec_duplicate_any(key)) { pos <- vctrs::vec_group_loc(key)$loc seq <- vector("integer", length = nrow(spec)) for (i in seq_along(pos)) { seq[pos[[i]]] <- seq_along(pos[[i]]) } spec$.seq <- seq } # Match spec to data, handling duplicated column names col_id <- vctrs::vec_match(names(df), spec$.name) has_match <- !is.na(col_id) if (!vctrs::vec_duplicate_any(col_id[has_match])) { return(spec) } spec <- vctrs::vec_slice(spec, col_id[has_match]) # Need to use numeric indices because names only match first spec$.name <- seq_along(df)[has_match] pieces <- vctrs::vec_split(seq_len(nrow(spec)), col_id[has_match]) copy <- integer(nrow(spec)) for (i in seq_along(pieces$val)) { idx <- pieces$val[[i]] copy[idx] <- seq_along(idx) } spec$.seq <- copy spec } # nocov end globalVariables(".") dbplyr/R/backend-mssql.R0000644000176200001440000003154314015731174014627 0ustar liggesusers#' Backend: SQL server #' #' @description #' See `vignette("translate-function")` and `vignette("translate-verb")` for #' details of overall translation technology. Key differences for this backend #' are: #' #' * `SELECT` uses `TOP` not `LIMIT` #' * Automatically prefixes `#` to create temporary tables. Add the prefix #' yourself to avoid the message. #' * String basics: `paste()`, `substr()`, `nchar()` #' * Custom types for `as.*` functions #' * Lubridate extraction functions, `year()`, `month()`, `day()` etc #' * Semi-automated bit <-> boolean translation (see below) #' #' Use `simulate_mssql()` with `lazy_frame()` to see simulated SQL without #' converting to live access database. #' #' @section Bit vs boolean: #' SQL server uses two incompatible types to represent `TRUE` and `FALSE` #' values: #' #' * The `BOOLEAN` type is the result of logical comparisons (e.g. `x > y`) #' and can be used `WHERE` but not to create new columns in `SELECT`. #' #' #' * The `BIT` type is a special type of numeric column used to store #' `TRUE` and `FALSE` values, but can't be used in `WHERE` clauses. #' #' #' dbplyr does its best to automatically create the correct type when needed, #' but can't do it 100% correctly because it does not have a full type #' inference system. This means that you many need to manually do conversions #' from time to time. #' #' * To convert from bit to boolean use `x == 1` #' * To convert from boolean to bit use `as.logical(if(x, 0, 1))` #' #' @param version Version of MS SQL to simulate. Currently only, difference is #' that 15.0 and above will use `TRY_CAST()` instead of `CAST()`. #' @name backend-mssql #' @aliases NULL #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_mssql()) #' lf %>% head() #' lf %>% transmute(x = paste(b, c, d)) #' #' # Can use boolean as is: #' lf %>% filter(c > d) #' # Need to convert from boolean to bit: #' lf %>% transmute(x = c > d) #' # Can use boolean as is: #' lf %>% transmute(x = ifelse(c > d, "c", "d")) NULL #' @export #' @rdname simulate_dbi simulate_mssql <- function(version = "15.0") { simulate_dbi("Microsoft SQL Server", version = numeric_version(version) ) } #' @export `dbplyr_edition.Microsoft SQL Server` <- function(con) { 2L } #' @export `sql_query_select.Microsoft SQL Server` <- function(con, select, from, where = NULL, group_by = NULL, having = NULL, order_by = NULL, limit = NULL, distinct = FALSE, ..., subquery = FALSE) { sql_select_clauses(con, select = sql_clause_select(con, select, distinct, top = limit), from = sql_clause_from(con, from), where = sql_clause_where(con, where), group_by = sql_clause_group_by(con, group_by), having = sql_clause_having(con, having), order_by = sql_clause_order_by(con, order_by, subquery, limit) ) } #' @export `sql_translation.Microsoft SQL Server` <- function(con) { mssql_scalar <- sql_translator(.parent = base_odbc_scalar, `!` = function(x) { if (mssql_needs_bit()) { x <- with_mssql_bool(x) mssql_as_bit(sql_expr(~ !!x)) } else { sql_expr(NOT(!!x)) } }, `!=` = mssql_infix_comparison("!="), `==` = mssql_infix_comparison("="), `<` = mssql_infix_comparison("<"), `<=` = mssql_infix_comparison("<="), `>` = mssql_infix_comparison(">"), `>=` = mssql_infix_comparison(">="), `&` = mssql_infix_boolean("&", "%AND%"), `&&` = mssql_infix_boolean("&", "%AND%"), `|` = mssql_infix_boolean("|", "%OR%"), `||` = mssql_infix_boolean("|", "%OR%"), `[` = function(x, i) { i <- with_mssql_bool(i) build_sql("CASE WHEN (", i, ") THEN (", x, ") END") }, bitwShiftL = sql_not_supported("bitwShiftL"), bitwShiftR = sql_not_supported("bitwShiftR"), `if` = mssql_sql_if, if_else = function(condition, true, false) mssql_sql_if(condition, true, false), ifelse = function(test, yes, no) mssql_sql_if(test, yes, no), case_when = mssql_case_when, as.logical = sql_cast("BIT"), as.Date = sql_cast("DATE"), as.numeric = sql_cast("FLOAT"), as.double = sql_cast("FLOAT"), as.character = sql_cast("VARCHAR(MAX)"), log = sql_prefix("LOG"), atan2 = sql_prefix("ATN2"), ceil = sql_prefix("CEILING"), ceiling = sql_prefix("CEILING"), # https://dba.stackexchange.com/questions/187090 pmin = sql_not_supported("pmin()"), pmax = sql_not_supported("pmax()"), is.null = mssql_is_null, is.na = mssql_is_null, # string functions ------------------------------------------------ nchar = sql_prefix("LEN"), paste = sql_paste_infix(" ", "+", function(x) sql_expr(cast(!!x %as% text))), paste0 = sql_paste_infix("", "+", function(x) sql_expr(cast(!!x %as% text))), substr = sql_substr("SUBSTRING"), substring = sql_substr("SUBSTRING"), # stringr functions str_length = sql_prefix("LEN"), str_c = sql_paste_infix("", "+", function(x) sql_expr(cast(!!x %as% text))), # no built in function: https://stackoverflow.com/questions/230138 str_to_title = sql_not_supported("str_to_title()"), # https://docs.microsoft.com/en-us/sql/t-sql/functions/substring-transact-sql?view=sql-server-ver15 str_sub = sql_str_sub("SUBSTRING", "LEN", optional_length = FALSE), # lubridate --------------------------------------------------------------- # https://en.wikibooks.org/wiki/SQL_Dialects_Reference/Functions_and_expressions/Date_and_time_functions as_date = sql_cast("DATE"), # Using DATETIME2 as it complies with ANSI and ISO. # MS recommends DATETIME2 for new work: # https://docs.microsoft.com/en-us/sql/t-sql/data-types/datetime-transact-sql?view=sql-server-2017 as_datetime = sql_cast("DATETIME2"), today = function() sql_expr(CAST(SYSDATETIME() %AS% DATE)), # https://docs.microsoft.com/en-us/sql/t-sql/functions/datepart-transact-sql?view=sql-server-2017 year = function(x) sql_expr(DATEPART(YEAR, !!x)), day = function(x) sql_expr(DATEPART(DAY, !!x)), mday = function(x) sql_expr(DATEPART(DAY, !!x)), yday = function(x) sql_expr(DATEPART(DAYOFYEAR, !!x)), hour = function(x) sql_expr(DATEPART(HOUR, !!x)), minute = function(x) sql_expr(DATEPART(MINUTE, !!x)), second = function(x) sql_expr(DATEPART(SECOND, !!x)), month = function(x, label = FALSE, abbr = TRUE) { if (!label) { sql_expr(DATEPART(MONTH, !!x)) } else { if (!abbr) { sql_expr(DATENAME(MONTH, !!x)) } else { stop("`abbr` is not supported in SQL Server translation", call. = FALSE) } } }, quarter = function(x, with_year = FALSE, fiscal_start = 1) { if (fiscal_start != 1) { stop("`fiscal_start` is not supported in SQL Server translation. Must be 1.", call. = FALSE) } if (with_year) { sql_expr((DATENAME(YEAR, !!x) + '.' + DATENAME(QUARTER, !!x))) } else { sql_expr(DATEPART(QUARTER, !!x)) } }, ) if (mssql_version(con) >= "11.0") { # MSSQL 2012 mssql_scalar <- sql_translator( .parent = mssql_scalar, as.logical = sql_try_cast("BIT"), as.Date = sql_try_cast("DATE"), as.POSIXct = sql_try_cast("TIMESTAMP"), as.numeric = sql_try_cast("FLOAT"), as.double = sql_try_cast("FLOAT"), # In SQL server, CAST (even with TRY) of INTEGER and BIGINT appears # fill entire columns with NULL if parsing single value fails: # https://gist.github.com/DavidPatShuiFong/7b47a9804a497b605e477f1bf6c38b37 # So we parse to NUMERIC (which doesn't have this problem), then to the # target type as.integer = function(x) { sql_expr(try_cast(try_cast(!!x %as% NUMERIC) %as% INT)) }, as.integer64 = function(x) { sql_expr(try_cast(try_cast(!!x %as% NUMERIC(38L, 0L)) %as% BIGINT)) }, as.character = sql_try_cast("VARCHAR(MAX)"), as_date = sql_try_cast("DATE"), as_datetime = sql_try_cast("DATETIME2") ) } sql_variant( mssql_scalar, sql_translator(.parent = base_odbc_agg, sd = sql_aggregate("STDEV", "sd"), var = sql_aggregate("VAR", "var"), # MSSQL does not have function for: cor and cov cor = sql_not_supported("cor()"), cov = sql_not_supported("cov()"), str_flatten = function(x, collapse = "") sql_expr(string_agg(!!x, !!collapse)) ), sql_translator(.parent = base_odbc_win, sd = win_aggregate("STDEV"), var = win_aggregate("VAR"), # MSSQL does not have function for: cor and cov cor = win_absent("cor"), cov = win_absent("cov"), str_flatten = function(x, collapse = "") { win_over( sql_expr(string_agg(!!x, !!collapse)), partition = win_current_group(), order = win_current_order() ) } ) )} mssql_version <- function(con) { if (inherits(con, "TestConnection")) { attr(con, "version") } else { numeric_version(DBI::dbGetInfo(con)$db.version) } } #' @export `sql_escape_raw.Microsoft SQL Server` <- function(con, x) { # SQL Server binary constants should be prefixed with 0x # https://docs.microsoft.com/en-us/sql/t-sql/data-types/constants-transact-sql?view=sql-server-ver15#binary-constants paste0(c("0x", format(x)), collapse = "") } #' @export `sql_table_analyze.Microsoft SQL Server` <- function(con, table, ...) { # https://docs.microsoft.com/en-us/sql/t-sql/statements/update-statistics-transact-sql build_sql("UPDATE STATISTICS ", as.sql(table, con = con), con = con) } # SQL server does not use CREATE TEMPORARY TABLE and instead prefixes # temporary table names with # # #' @export `db_table_temporary.Microsoft SQL Server` <- function(con, table, temporary) { if (temporary && substr(table, 1, 1) != "#") { table <- hash_temp(table) } list( table = table, temporary = FALSE ) } #' @export `sql_query_save.Microsoft SQL Server` <- function(con, sql, name, temporary = TRUE, ...){ # https://stackoverflow.com/q/16683758/946850 build_sql( "SELECT * INTO ", as.sql(name, con), " ", "FROM (", sql, ") AS temp", con = con ) } # Bit vs boolean ---------------------------------------------------------- mssql_needs_bit <- function() { context <- sql_current_context() identical(context$clause, "SELECT") || identical(context$clause, "ORDER") } with_mssql_bool <- function(code) { local_context(list(clause = "")) code } mssql_as_bit <- function(x) { if (mssql_needs_bit()) { sql_expr(cast(iif(!!x, 1L, 0L) %as% BIT)) } else { x } } mssql_is_null <- function(x) { mssql_as_bit(sql_is_null({{x}})) } mssql_infix_comparison <- function(f) { assert_that(is_string(f)) f <- toupper(f) function(x, y) { mssql_as_bit(build_sql(x, " ", sql(f), " ", y)) } } mssql_infix_boolean <- function(if_bit, if_bool) { force(if_bit) force(if_bool) function(x, y) { if (mssql_needs_bit()) { x <- with_mssql_bool(x) y <- with_mssql_bool(y) mssql_as_bit(sql_call2(if_bool, x, y)) } else { sql_call2(if_bool, x, y) } } } mssql_sql_if <- function(cond, if_true, if_false = NULL) { cond <- with_mssql_bool(cond) sql_expr(IIF(!!cond, !!if_true, !!if_false)) } mssql_case_when <- function(...) { with_mssql_bool(sql_case_when(...)) } #' @export `sql_escape_logical.Microsoft SQL Server` <- function(con, x) { if (mssql_needs_bit()) { y <- ifelse(x, "1", "0") } else { y <- as.character(x) } y[is.na(x)] <- "NULL" y } globalVariables(c("BIT", "CAST", "%AS%", "%is%", "convert", "DATE", "DATENAME", "DATEPART", "IIF", "NOT", "SUBSTRING", "LTRIM", "RTRIM", "CHARINDEX", "SYSDATETIME", "SECOND", "MINUTE", "HOUR", "DAY", "DAYOFWEEK", "DAYOFYEAR", "MONTH", "QUARTER", "YEAR", "BIGINT", "INT")) dbplyr/R/progress.R0000644000176200001440000000520614002647450013743 0ustar liggesusers# Copy in deprecated progress_estimated() from dplyr progress_estimated <- function(n, min_time = 0) { Progress$new(n, min_time = min_time) } #' @importFrom R6 R6Class Progress <- R6::R6Class("Progress", public = list( n = NULL, i = 0, init_time = NULL, stopped = FALSE, stop_time = NULL, min_time = NULL, last_update = NULL, initialize = function(n, min_time = 0, ...) { self$n <- n self$min_time <- min_time self$begin() }, begin = function() { "Initialise timer. Call this before beginning timing." self$i <- 0 self$last_update <- self$init_time <- now() self$stopped <- FALSE self }, pause = function(x) { "Sleep for x seconds. Useful for testing." Sys.sleep(x) self }, width = function() { getOption("width") - nchar("|100% ~ 99.9 h remaining") - 2 }, tick = function() { "Process one element" if (self$stopped) return(self) if (self$i == self$n) abort("No more ticks") self$i <- self$i + 1 self }, stop = function() { if (self$stopped) return(self) self$stopped <- TRUE self$stop_time <- now() self }, print = function(...) { if (!isTRUE(getOption("dplyr.show_progress")) || # user sepecifies no progress !interactive() || # not an interactive session !is.null(getOption("knitr.in.progress"))) { # dplyr used within knitr document return(invisible(self)) } now_ <- now() if (now_ - self$init_time < self$min_time || now_ - self$last_update < 0.05) { return(invisible(self)) } self$last_update <- now_ if (self$stopped) { overall <- show_time(self$stop_time - self$init_time) if (self$i == self$n) { cat_line("Completed after ", overall) cat("\n") } else { cat_line("Killed after ", overall) cat("\n") } return(invisible(self)) } avg <- (now() - self$init_time) / self$i time_left <- (self$n - self$i) * avg nbars <- trunc(self$i / self$n * self$width()) cat_line( "|", str_rep("=", nbars), str_rep(" ", self$width() - nbars), "|", format(round(self$i / self$n * 100), width = 3), "% ", "~", show_time(time_left), " remaining" ) invisible(self) } ) ) str_rep <- function(x, i) { paste(rep.int(x, i), collapse = "") } show_time <- function(x) { if (x < 60) { paste(round(x), "s") } else if (x < 60 * 60) { paste(round(x / 60), "m") } else { paste(round(x / (60 * 60)), "h") } } now <- function() proc.time()[[3]] dbplyr/R/verb-uncount.R0000644000176200001440000000544514004012136014520 0ustar liggesusers#' "Uncount" a database table #' #' This is a method for the tidyr `uncount()` generic. It uses a temporary #' table, so your database user needs permissions to create one. #' #' @inheritParams arrange.tbl_lazy #' @param weights A vector of weights. Evaluated in the context of `data`; #' supports quasiquotation. #' @param .id Supply a string to create a new variable which gives a unique #' identifier for each created row. #' @param .remove If `TRUE`, and `weights` is the name of a column in `data`, #' then this column is removed. #' @export #' @examples #' df <- memdb_frame(x = c("a", "b"), n = c(1, 2)) #' dbplyr_uncount(df, n) #' dbplyr_uncount(df, n, .id = "id") #' #' # You can also use constants #' dbplyr_uncount(df, 2) #' #' # Or expressions #' dbplyr_uncount(df, 2 / n) dbplyr_uncount <- function(data, weights, .remove = TRUE, .id = NULL) { # Overview of the strategy: # 1. calculate `n_max`, the max weight # 2. create a temporary db table with ids from 1 to `n_max` # 3. duplicate data by inner joining with this temporary table under the # condition that data$weight <= tmp$id # # An alternative approach would be to use a recursive CTE but this requires # more code and testing across backends. # See https://stackoverflow.com/questions/33327837/repeat-rows-n-times-according-to-column-value weights_quo <- enquo(weights) weights_is_col <- quo_is_symbol(weights_quo) && quo_name(weights_quo) %in% colnames(data) if (weights_is_col) { weights_col <- quo_name(weights_quo) } else { weights_col <- "..dbplyr_weight_col" data <- mutate( data, !!weights_col := !!weights_quo, ) } n_max <- summarise(ungroup(data), max(!!sym(weights_col), na.rm = TRUE)) %>% pull() n_max <- vctrs::vec_cast(n_max, integer(), x_arg = "weights") if (is_null(.id)) { row_id_col <- "..dbplyr_row_id" } else { row_id_col <- .id } sql_on_expr <- expr(RHS[[!!row_id_col]] <= LHS[[!!weights_col]]) con <- remote_con(data) helper_table <- db_copy_to( con = con, table = unique_table_name(), values = tibble(!!row_id_col := seq2(1, n_max)), temporary = TRUE, in_transaction = FALSE ) data_uncounted <- inner_join( data, tbl(con, helper_table), by = character(), sql_on = translate_sql(!!sql_on_expr, con = con) ) cols_to_remove <- character() if (is_null(.id)) { cols_to_remove <- c(cols_to_remove, row_id_col) } grps <- group_vars(data_uncounted) if (!weights_is_col || (weights_is_col && .remove)) { cols_to_remove <- c(cols_to_remove, weights_col) # need to regroup to be able to remove weights_col grps <- setdiff(grps, weights_col) } data_uncounted %>% ungroup() %>% select(-all_of(cols_to_remove)) %>% group_by(!!!syms(grps)) } globalVariables(c("RHS", "LHS", "all_of")) dbplyr/R/backend-postgres-old.R0000644000176200001440000000251014004012136016066 0ustar liggesusers#' @include backend-postgres.R NULL # Use dbplyr edition 1 for custom method dispatch on RPostgreSQL connections #' @export dbplyr_edition.PostgreSQLConnection <- function(con) { 1L } #' @export db_write_table.PostgreSQLConnection <- function(con, table, types, values, temporary = TRUE, overwrite = FALSE, ....) { if (!isFALSE(temporary)) { abort(c( "RPostgreSQL backend does not support creation of temporary tables`", i = "Either set `temporary = FALSE` or switch to RPostgres" )) } dbWriteTable( con, name = table, value = values, field.types = types, overwrite = overwrite, row.names = FALSE ) table } #' @export db_query_fields.PostgreSQLConnection <- function(con, sql, ...) { fields <- build_sql( "SELECT * FROM ", sql_subquery(con, sql), " WHERE 0=1", con = con ) qry <- dbSendQuery(con, fields) on.exit(dbClearResult(qry)) dbGetInfo(qry)$fieldDescription[[1]]$name } #' @export db_connection_describe.PostgreSQLConnection <- db_connection_describe.PqConnection #' @export sql_translation.PostgreSQLConnection <- sql_translation.PqConnection #' @export sql_expr_matches.PostgreSQLConnection <- sql_expr_matches.PqConnection #' @export sql_query_explain.PostgreSQLConnection <- sql_query_explain.PqConnection dbplyr/R/db-sql.R0000644000176200001440000003106714015731174013266 0ustar liggesusers#' SQL generation generics #' #' @description #' #' SQL translation: #' #' * `sql_expr_matches(con, x, y)` generates an alternative to `x = y` when a #' pair of `NULL`s should match. The default translation uses a `CASE WHEN` #' as described in . #' #' * `sql_translation(con)` generates a SQL translation environment. #' #' Tables: #' #' * `sql_table_analyze(con, table)` generates SQL that "analyzes" the table, #' ensuring that the database has up-to-date statistics for use in the query #' planner. It called from [copy_to()] when `analyze = TRUE`. #' #' * `sql_table_index()` generates SQL for adding an index to table. The #' #' Query manipulation: #' #' * `sql_query_explain(con, sql)` generates SQL that "explains" a query, #' i.e. generates a query plan describing what indexes etc that the #' database will use. #' #' * `sql_query_fields()` generates SQL for a 0-row result that is used to #' capture field names in [tbl_sql()] #' #' * `sql_query_save(con, sql)` generates SQL for saving a query into a #' (temporary) table. #' #' * `sql_query_wrap(con, from)` generates SQL for wrapping a query into a #' subquery. #' #' Query generation: #' #' * `sql_query_select()` generate SQL for a `SELECT` query #' * `sql_query_join()` generate SQL for joins #' * `sql_query_semi_join()` generate SQL for semi- and anti-joins #' * `sql_query_set_op()` generate SQL for `UNION`, `INTERSECT`, and `EXCEPT` #' queries. #' #' @section dbplyr 2.0.0: #' #' Many `dplyr::db_*` generics have been replaced by `dbplyr::sql_*` generics. #' To update your backend, you'll need to extract the SQL generation out of your #' existing code, and place it in a new method for a dbplyr `sql_` generic. #' #' * `dplyr::db_analyze()` is replaced by `dbplyr::sql_table_analyze()` #' * `dplyr::db_explain()` is replaced by `dbplyr::sql_query_explain()` #' * `dplyr::db_create_index()` is replaced by `dbplyr::sql_table_index()` #' * `dplyr::db_query_fields()` is replaced by `dbplyr::sql_query_fields()` #' * `dplyr::db_query_rows()` is no longer used; you can delete it #' * `dplyr::db_save_query()` is replaced by `dbplyr::sql_query_save()` #' #' The query generating functions have also changed names. Their behaviour is #' unchanged, so you just need to rename the generic and import from dbplyr #' instead of dplyr. #' #' * `dplyr::sql_select()` is replaced by `dbplyr::sql_query_select()` #' * `dplyr::sql_join()` is replaced by `dbplyr::sql_query_join()` #' * `dplyr::sql_semi_join()` is replaced by `dbplyr::sql_query_semi_join()` #' * `dplyr::sql_set_op()` is replaced by `dbplyr::sql_query_set_op()` #' * `dplyr::sql_subquery()` is replaced by `dbplyr::sql_query_wrap()` #' #' Learn more in `vignette("backend-2.0")` #' #' @keywords internal #' @family generic #' @name db-sql NULL #' @export #' @rdname db-sql sql_expr_matches <- function(con, x, y) { UseMethod("sql_expr_matches") } # https://modern-sql.com/feature/is-distinct-from #' @export sql_expr_matches.DBIConnection <- function(con, x, y) { build_sql( "CASE WHEN (", x, " = ", y, ") OR (", x, " IS NULL AND ", y, " IS NULL) ", "THEN 0 ", "ELSE 1 = 0", con = con ) } #' @export #' @rdname db-sql sql_translation <- function(con) { UseMethod("sql_translation") } # sql_translation.DBIConnection lives in backend-.R dbplyr_sql_translation <- function(con) { dbplyr_fallback(con, "sql_translate_env") } #' @importFrom dplyr sql_translate_env #' @export sql_translate_env.DBIConnection <- function(con) { sql_translation(con) } # Tables ------------------------------------------------------------------ #' @rdname db-sql #' @export sql_table_analyze <- function(con, table, ...) { UseMethod("sql_table_analyze") } #' @export sql_table_analyze.DBIConnection <- function(con, table, ...) { build_sql("ANALYZE ", as.sql(table, con = con), con = con) } #' @rdname db-sql #' @export sql_table_index <- function(con, table, columns, name = NULL, unique = FALSE, ...) { UseMethod("sql_table_index") } #' @export sql_table_index.DBIConnection <- function(con, table, columns, name = NULL, unique = FALSE, ...) { assert_that(is_string(table) | is.schema(table), is.character(columns)) name <- name %||% paste0(c(unclass(table), columns), collapse = "_") fields <- escape(ident(columns), parens = TRUE, con = con) build_sql( "CREATE ", if (unique) sql("UNIQUE "), "INDEX ", as.sql(name, con = con), " ON ", as.sql(table, con = con), " ", fields, con = con ) } # Query manipulation ------------------------------------------------------ #' @rdname db-sql #' @export sql_query_explain <- function(con, sql, ...) { UseMethod("sql_query_explain") } #' @export sql_query_explain.DBIConnection <- function(con, sql, ...) { build_sql("EXPLAIN ", sql, con = con) } #' @rdname db-sql #' @export sql_query_fields <- function(con, sql, ...) { UseMethod("sql_query_fields") } #' @export sql_query_fields.DBIConnection <- function(con, sql, ...) { dbplyr_query_select(con, sql("*"), dbplyr_sql_subquery(con, sql), where = sql("0 = 1")) } #' @rdname db-sql #' @export sql_query_save <- function(con, sql, name, temporary = TRUE, ...) { UseMethod("sql_query_save") } #' @export sql_query_save.DBIConnection <- function(con, sql, name, temporary = TRUE, ...) { build_sql( "CREATE ", if (temporary) sql("TEMPORARY "), "TABLE \n", as.sql(name, con), " AS ", sql, con = con ) } #' @export #' @rdname db-sql sql_query_wrap <- function(con, from, name = unique_subquery_name(), ...) { UseMethod("sql_query_wrap") } #' @export sql_query_wrap.DBIConnection <- function(con, from, name = unique_subquery_name(), ...) { if (is.ident(from)) { setNames(from, name) } else if (is.schema(from)) { setNames(as.sql(from, con), name) } else { build_sql("(", from, ") ", ident(name %||% unique_subquery_name()), con = con) } } #' @rdname db-sql #' @export sql_query_rows <- function(con, sql, ...) { UseMethod("sql_query_rows") } #' @export sql_query_rows.DBIConnection <- function(con, sql, ...) { from <- dbplyr_sql_subquery(con, sql, "master") build_sql("SELECT COUNT(*) FROM ", from, con = con) } # Query generation -------------------------------------------------------- #' @rdname db-sql #' @export sql_query_select <- function(con, select, from, where = NULL, group_by = NULL, having = NULL, order_by = NULL, limit = NULL, distinct = FALSE, ..., subquery = FALSE) { UseMethod("sql_query_select") } #' @export sql_query_select.DBIConnection <- function(con, select, from, where = NULL, group_by = NULL, having = NULL, order_by = NULL, limit = NULL, distinct = FALSE, ..., subquery = FALSE) { sql_select_clauses(con, select = sql_clause_select(con, select, distinct), from = sql_clause_from(con, from), where = sql_clause_where(con, where), group_by = sql_clause_group_by(con, group_by), having = sql_clause_having(con, having), order_by = sql_clause_order_by(con, order_by, subquery, limit), limit = sql_clause_limit(con, limit) ) } dbplyr_query_select <- function(con, ...) { dbplyr_fallback(con, "sql_select", ...) } #' @importFrom dplyr sql_select #' @export sql_select.DBIConnection <- function(con, select, from, where = NULL, group_by = NULL, having = NULL, order_by = NULL, limit = NULL, distinct = FALSE, ..., subquery = FALSE) { sql_query_select( con, select, from, where = where, group_by = group_by, having = having, order_by = order_by, limit = limit, distinct = distinct, ..., subquery = subquery ) } #' @rdname db-sql #' @export sql_query_join <- function(con, x, y, vars, type = "inner", by = NULL, na_matches = FALSE, ...) { UseMethod("sql_query_join") } #' @export sql_query_join.DBIConnection <- function(con, x, y, vars, type = "inner", by = NULL, na_matches = FALSE, ...) { JOIN <- switch( type, left = sql("LEFT JOIN"), inner = sql("INNER JOIN"), right = sql("RIGHT JOIN"), full = sql("FULL JOIN"), cross = sql("CROSS JOIN"), stop("Unknown join type:", type, call. = FALSE) ) select <- sql_join_vars(con, vars) on <- sql_join_tbls(con, by, na_matches = na_matches) # Wrap with SELECT since callers assume a valid query is returned build_sql( "SELECT ", select, "\n", "FROM ", x, "\n", JOIN, " ", y, "\n", if (!is.null(on)) build_sql("ON ", on, "\n", con = con) else NULL, con = con ) } dbplyr_query_join <- function(con, ...) { dbplyr_fallback(con, "sql_join", ...) } #' @export #' @importFrom dplyr sql_join sql_join.DBIConnection <- function(con, x, y, vars, type = "inner", by = NULL, na_matches = FALSE, ...) { sql_query_join( con, x, y, vars, type = type, by = by, na_matches = na_matches, ... ) } #' @rdname db-sql #' @export sql_query_semi_join <- function(con, x, y, anti = FALSE, by = NULL, ...) { UseMethod("sql_query_semi_join") } #' @export sql_query_semi_join.DBIConnection <- function(con, x, y, anti = FALSE, by = NULL, ...) { lhs <- escape(ident("LHS"), con = con) rhs <- escape(ident("RHS"), con = con) on <- sql_join_tbls(con, by) build_sql( "SELECT * FROM ", x, "\n", "WHERE ", if (anti) sql("NOT "), "EXISTS (\n", " SELECT 1 FROM ", y, "\n", " WHERE ", on, "\n", ")", con = con ) } dbplyr_query_semi_join <- function(con, ...) { dbplyr_fallback(con, "sql_semi_join", ...) } #' @export #' @importFrom dplyr sql_semi_join sql_semi_join.DBIConnection <- function(con, x, y, anti = FALSE, by = NULL, ...) { sql_query_semi_join(con, x, y, anti = anti, by = by, ...) } #' @rdname db-sql #' @export sql_query_set_op <- function(con, x, y, method, ..., all = FALSE) { UseMethod("sql_query_set_op") } #' @export sql_query_set_op.DBIConnection <- function(con, x, y, method, ..., all = FALSE) { build_sql( "(", x, ")", "\n", sql(method), if (all) sql(" ALL"), "\n", "(", y, ")", con = con ) } dbplyr_query_set_op <- function(con, ...) { dbplyr_fallback(con, "sql_set_op", ...) } #' @importFrom dplyr sql_set_op #' @export sql_set_op.DBIConnection <- function(con, x, y, method) { # dplyr::sql_set_op() doesn't have ... sql_query_set_op(con, x, y, method) } # dplyr fallbacks --------------------------------------------------------- dbplyr_analyze <- function(con, ...) { dbplyr_fallback(con, "db_analyze", ...) } #' @export #' @importFrom dplyr db_analyze db_analyze.DBIConnection <- function(con, table, ...) { sql <- sql_table_analyze(con, table, ...) if (is.null(sql)) { return() } dbExecute(con, sql) } dbplyr_create_index <- function(con, ...) { dbplyr_fallback(con, "db_create_index", ...) } #' @export #' @importFrom dplyr db_create_index db_create_index.DBIConnection <- function(con, table, columns, name = NULL, unique = FALSE, ...) { sql <- sql_table_index(con, table, columns, name = name, unique = unique, ...) dbExecute(con, sql) } dbplyr_explain <- function(con, ...) { dbplyr_fallback(con, "db_explain", ...) } #' @export #' @importFrom dplyr db_explain db_explain.DBIConnection <- function(con, sql, ...) { sql <- sql_query_explain(con, sql, ...) expl <- dbGetQuery(con, sql) out <- utils::capture.output(print(expl)) paste(out, collapse = "\n") } dbplyr_query_fields <- function(con, ...) { dbplyr_fallback(con, "db_query_fields", ...) } #' @export #' @importFrom dplyr db_query_fields db_query_fields.DBIConnection <- function(con, sql, ...) { sql <- sql_query_fields(con, sql, ...) names(dbGetQuery(con, sql)) } dbplyr_save_query <- function(con, ...) { dbplyr_fallback(con, "db_save_query", ...) } #' @export #' @importFrom dplyr db_save_query db_save_query.DBIConnection <- function(con, sql, name, temporary = TRUE, ...) { sql <- sql_query_save(con, sql, name, temporary = temporary, ...) dbExecute(con, sql, immediate = TRUE) name } dbplyr_sql_subquery <- function(con, ...) { dbplyr_fallback(con, "sql_subquery", ...) } #' @export #' @importFrom dplyr sql_subquery sql_subquery.DBIConnection <- function(con, from, name = unique_subquery_name(), ...) { sql_query_wrap(con, from = from, name = name, ...) } dbplyr/R/translate-sql-window.R0000644000176200001440000002023114015731174016172 0ustar liggesusers#' Generate SQL expression for window functions #' #' `win_over()` makes it easy to generate the window function specification. #' `win_absent()`, `win_rank()`, `win_aggregate()`, and `win_cumulative()` #' provide helpers for constructing common types of window functions. #' `win_current_group()` and `win_current_order()` allow you to access #' the grouping and order context set up by [group_by()] and [arrange()]. #' #' @param expr The window expression #' @param parition Variables to partition over #' @param order Variables to order by #' @param frame A numeric vector of length two defining the frame. #' @param f The name of an sql function as a string #' @export #' @keywords internal #' @examples #' con <- simulate_dbi() #' #' win_over(sql("avg(x)"), con = con) #' win_over(sql("avg(x)"), "y", con = con) #' win_over(sql("avg(x)"), order = "y", con = con) #' win_over(sql("avg(x)"), order = c("x", "y"), con = con) #' win_over(sql("avg(x)"), frame = c(-Inf, 0), order = "y", con = con) win_over <- function(expr, partition = NULL, order = NULL, frame = NULL, con = sql_current_con()) { if (length(partition) > 0) { partition <- as.sql(partition, con = con) partition <- build_sql( "PARTITION BY ", sql_vector( escape(partition, con = con), collapse = ", ", parens = FALSE, con = con ), con = con ) } if (length(order) > 0) { order <- as.sql(order, con = con) order <- build_sql( "ORDER BY ", sql_vector( escape(order, con = con), collapse = ", ", parens = FALSE, con = con ), con = con ) } if (length(frame) > 0) { if (length(order) == 0) { warning( "Windowed expression '", expr, "' does not have explicit order.\n", "Please use arrange() or window_order() to make determinstic.", call. = FALSE ) } if (is.numeric(frame)) frame <- rows(frame[1], frame[2]) frame <- build_sql("ROWS ", frame, con = con) } over <- sql_vector(purrr::compact(list(partition, order, frame)), parens = TRUE, con = con) sql <- build_sql(expr, " OVER ", over, con = con) sql } rows <- function(from = -Inf, to = 0) { if (from >= to) stop("from must be less than to", call. = FALSE) dir <- function(x) if (x < 0) "PRECEDING" else "FOLLOWING" val <- function(x) if (is.finite(x)) as.integer(abs(x)) else "UNBOUNDED" bound <- function(x) { if (x == 0) return("CURRENT ROW") paste(val(x), dir(x)) } if (to == 0) { sql(bound(from)) } else { sql(paste0("BETWEEN ", bound(from), " AND ", bound(to))) } } #' @rdname win_over #' @export win_rank <- function(f) { force(f) function(order = NULL) { win_over( build_sql(sql(f), list()), partition = win_current_group(), order = order %||% win_current_order(), frame = win_current_frame() ) } } #' @rdname win_over #' @export win_aggregate <- function(f) { force(f) warned <- FALSE function(x, na.rm = FALSE) { warned <<- check_na_rm(f, na.rm, warned) frame <- win_current_frame() win_over( build_sql(sql(f), list(x)), partition = win_current_group(), order = if (!is.null(frame)) win_current_order(), frame = frame ) } } #' @rdname win_over #' @export win_aggregate_2 <- function(f) { function(x, y) { frame <- win_current_frame() win_over( build_sql(sql(f), list(x, y)), partition = win_current_group(), order = if (!is.null(frame)) win_current_order(), frame = frame ) } } #' @rdname win_over #' @usage NULL #' @export win_recycled <- win_aggregate #' @rdname win_over #' @export win_cumulative <- function(f) { force(f) function(x, order = NULL) { win_over( build_sql(sql(f), list(x)), partition = win_current_group(), order = order %||% win_current_order(), frame = c(-Inf, 0) ) } } #' @rdname win_over #' @export win_absent <- function(f) { force(f) function(...) { stop( "Window function `", f, "()` is not supported by this database", call. = FALSE ) } } # API to set default partitioning etc ------------------------------------- # Use a global variable to communicate state of partitioning between # tbl and sql translator. This isn't the most amazing design, but it keeps # things loosely coupled and is easy to understand. sql_context <- new.env(parent = emptyenv()) sql_context$group_by <- NULL sql_context$order_by <- NULL sql_context$con <- NULL # Used to carry additional information needed for special cases sql_context$context <- list() set_current_con <- function(con) { old <- sql_context$con sql_context$con <- con invisible(old) } local_con <- function(con, env = parent.frame()) { old <- set_current_con(con) withr::defer(set_current_con(old), envir = env) invisible() } set_win_current_group <- function(vars) { stopifnot(is.null(vars) || is.character(vars)) old <- sql_context$group_by sql_context$group_by <- vars invisible(old) } set_win_current_order <- function(vars) { stopifnot(is.null(vars) || is.character(vars)) old <- sql_context$order_by sql_context$order_by <- vars invisible(old) } set_win_current_frame <- function(frame) { stopifnot(is.null(frame) || is.numeric(frame)) old <- sql_context$frame sql_context$frame <- frame invisible(old) } #' @export #' @rdname win_over win_current_group <- function() sql_context$group_by #' @export #' @rdname win_over win_current_order <- function() sql_context$order_by #' @export #' @rdname win_over win_current_frame <- function() sql_context$frame # Not exported, because you shouldn't need it sql_current_con <- function() { sql_context$con } # Functions to manage information for special cases set_current_context <- function(context) { old <- sql_context$context sql_context$context <- context invisible(old) } sql_current_context <- function() sql_context$context local_context <- function(x, env = parent.frame()) { old <- set_current_context(x) withr::defer(set_current_context(old), envir = env) invisible() } # Where translation ------------------------------------------------------- uses_window_fun <- function(x, con) { if (is.null(x)) return(FALSE) if (is.list(x)) { calls <- unlist(lapply(x, all_calls)) } else { calls <- all_calls(x) } win_f <- ls(envir = dbplyr_sql_translation(con)$window) any(calls %in% win_f) } common_window_funs <- function() { ls(dbplyr_sql_translation(NULL)$window) } #' @noRd #' @examples #' translate_window_where(quote(1)) #' translate_window_where(quote(x)) #' translate_window_where(quote(x == 1)) #' translate_window_where(quote(x == 1 && y == 2)) #' translate_window_where(quote(n() > 10)) #' translate_window_where(quote(rank() > cumsum(AB))) translate_window_where <- function(expr, window_funs = common_window_funs()) { switch(typeof(expr), logical = , integer = , double = , complex = , character = , string = , symbol = window_where(expr, list()), language = { if (is_formula(expr)) { translate_window_where(f_rhs(expr), window_funs) } else if (is_call(expr, name = window_funs)) { name <- unique_subquery_name() window_where(sym(name), set_names(list(expr), name)) } else { args <- lapply(expr[-1], translate_window_where, window_funs = window_funs) expr <- call2(node_car(expr), splice(lapply(args, "[[", "expr"))) window_where( expr = expr, comp = unlist(lapply(args, "[[", "comp"), recursive = FALSE) ) } }, abort(glue("Unknown type: ", typeof(expr))) ) } #' @noRd #' @examples #' translate_window_where_all(list(quote(x == 1), quote(n() > 2))) #' translate_window_where_all(list(quote(cumsum(x) == 10), quote(n() > 2))) translate_window_where_all <- function(x, window_funs = common_window_funs()) { out <- lapply(x, translate_window_where, window_funs = window_funs) list( expr = unlist(lapply(out, "[[", "expr"), recursive = FALSE), comp = unlist(lapply(out, "[[", "comp"), recursive = FALSE) ) } window_where <- function(expr, comp) { stopifnot(is.call(expr) || is.name(expr) || is.atomic(expr)) stopifnot(is.list(comp)) list( expr = expr, comp = comp ) } dbplyr/R/data-nycflights13.R0000644000176200001440000000417114002647450015324 0ustar liggesusers#' Database versions of the nycflights13 data #' #' These functions cache the data from the `nycflights13` database in #' a local database, for use in examples and vignettes. Indexes are created #' to making joining tables on natural keys efficient. #' #' @keywords internal #' @name nycflights13 NULL #' @export #' @rdname nycflights13 #' @param path location of SQLite database file nycflights13_sqlite <- function(path = NULL) { cache_computation("nycflights_sqlite", { path <- db_location(path, "nycflights13.sqlite") message("Caching nycflights db at ", path) con <- DBI::dbConnect(RSQLite::SQLite(), dbname = path, create = TRUE) copy_nycflights13(con) }) } #' @export #' @rdname nycflights13 #' @param dbname,... Arguments passed on to [src_postgres()] nycflights13_postgres <- function(dbname = "nycflights13", ...) { cache_computation("nycflights_postgres", { message("Caching nycflights db in postgresql db ", dbname) con <- DBI::dbConnect(RPostgres::Postgres(), dbname = dbname, ...) copy_nycflights13(con) }) } #' @rdname nycflights13 #' @export has_nycflights13 <- function(type = c("sqlite", "postgresql"), ...) { if (!requireNamespace("nycflights13", quietly = TRUE)) return(FALSE) type <- match.arg(type) succeeds(switch( type, sqlite = nycflights13_sqlite(...), postgres = nycflights13_postgres(...) ), quiet = TRUE) } #' @export #' @rdname nycflights13 copy_nycflights13 <- function(con, ...) { all <- utils::data(package = "nycflights13")$results[, 3] unique_index <- list( airlines = list("carrier"), planes = list("tailnum") ) index <- list( airports = list("faa"), flights = list(c("year", "month", "day"), "carrier", "tailnum", "origin", "dest"), weather = list(c("year", "month", "day"), "origin") ) tables <- setdiff(all, DBI::dbListTables(con)) # Create missing tables for (table in tables) { df <- getExportedValue("nycflights13", table) message("Creating table: ", table) copy_to( con, df, table, unique_indexes = unique_index[[table]], indexes = index[[table]], temporary = FALSE ) } con } dbplyr/R/verb-fill.R0000644000176200001440000001563214004012136013752 0ustar liggesusers#' Fill in missing values with previous or next value #' #' @inheritParams arrange.tbl_lazy #' @param ... Columns to fill. #' @param .direction Direction in which to fill missing values. Currently #' either "down" (the default) or "up". Note that "up" does not work when #' `.data` is sorted by non-numeric columns. As a workaround revert the order #' yourself beforehand; for example replace `arrange(x, desc(y))` by #' `arrange(desc(x), y)`. #' #' @examples #' squirrels <- tibble::tribble( #' ~group, ~name, ~role, ~n_squirrels, ~ n_squirrels2, #' 1, "Sam", "Observer", NA, 1, #' 1, "Mara", "Scorekeeper", 8, NA, #' 1, "Jesse", "Observer", NA, NA, #' 1, "Tom", "Observer", NA, 4, #' 2, "Mike", "Observer", NA, NA, #' 2, "Rachael", "Observer", NA, 6, #' 2, "Sydekea", "Scorekeeper", 14, NA, #' 2, "Gabriela", "Observer", NA, NA, #' 3, "Derrick", "Observer", NA, NA, #' 3, "Kara", "Scorekeeper", 9, 10, #' 3, "Emily", "Observer", NA, NA, #' 3, "Danielle", "Observer", NA, NA #' ) #' squirrels$id <- 1:12 #' #' if (require("tidyr", quietly = TRUE)) { #' tbl_memdb(squirrels) %>% #' window_order(id) %>% #' tidyr::fill( #' n_squirrels, #' n_squirrels2, #' ) #' } fill.tbl_lazy <- function(.data, ..., .direction = c("down", "up")) { sim_data <- simulate_vars(.data) cols_to_fill <- syms(names(tidyselect::eval_select(expr(c(...)), sim_data))) order_by_cols <- op_sort(.data) .direction <- arg_match0(.direction, c("down", "up")) if (is_empty(order_by_cols)) { abort( c( x = "`.data` does not have explicit order.", i = "Please use arrange() or window_order() to make determinstic." ) ) } if (.direction == "up") { # `desc()` cannot be used here because one gets invalid SQL if a variable # is already wrapped in `desc()` # i.e. `desc(desc(x))` produces `x DESC DESC` # but this implies that "up" does not work for sorting non-numeric columns! order_by_cols <- purrr::map(order_by_cols, ~ quo(-!!.x)) } dbplyr_fill0( .con = remote_con(.data), .data = .data, cols_to_fill = cols_to_fill, order_by_cols = order_by_cols, .direction = .direction ) } dbplyr_fill0 <- function(.con, .data, cols_to_fill, order_by_cols, .direction) { UseMethod("dbplyr_fill0") } # databases with support for `IGNORE NULLS` # * hive: https://cwiki.apache.org/confluence/display/Hive/LanguageManual+WindowingAndAnalytics # * impala: https://docs.cloudera.com/documentation/enterprise/5-11-x/topics/impala_analytic_functions.html # * mssql: https://docs.microsoft.com/en-us/sql/t-sql/functions/first-value-transact-sql?view=sql-server-ver15 # * oracle: https://oracle-base.com/articles/misc/first-value-and-last-value-analytic-functions # * redshift: https://docs.aws.amazon.com/redshift/latest/dg/r_WF_first_value.html # * teradata: https://docs.teradata.com/r/756LNiPSFdY~4JcCCcR5Cw/V~t1FC7orR6KCff~6EUeDQ #' @export dbplyr_fill0.DBIConnection <- function(.con, .data, cols_to_fill, order_by_cols, .direction) { # strategy: # 1. construct a window # * from the first row to the current row # * ordered by `order_by_cols` # * partitioned by groups if any # 2. in this window use the last value ignoring nulls; in SQL this is (usually) # `LAST_VALUE( IGNORE NULLS)` # # Source: https://dzone.com/articles/how-to-fill-sparse-data-with-the-previous-non-empt grps <- op_grps(.data) fill_sql <- purrr::map( cols_to_fill, ~ win_over( last_value_sql(.con, .x), partition = if (!is_empty(grps)) escape(ident(op_grps(.data)), con = .con), order = translate_sql(!!!order_by_cols, con = .con), con = .con ) ) %>% set_names(as.character(cols_to_fill)) .data %>% transmute( !!!syms(colnames(.data)), !!!fill_sql ) } # databases without support for `IGNORE NULLS` # * sqlite # * access: https://www.techonthenet.com/access/functions/ # * hana: https://help.sap.com/viewer/4fe29514fd584807ac9f2a04f6754767/2.0.04/en-US/e7ef7cc478f14a408e1af27fc1a78624.html # * mysql: https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html # * mariadb # * postgres: https://www.postgresql.org/docs/13/functions-window.html #' @export dbplyr_fill0.SQLiteConnection <- function(.con, .data, cols_to_fill, order_by_cols, .direction) { # this strategy is used for databases that don't support `IGNORE NULLS` in # `LAST_VALUE()`. # # strategy: # for each column to fill: # 1. generate a helper column `....dbplyr_partion_x`. It creates one partition # per non-NA value and all following NA (in the order of `order_by_cols`), # i.e. each partition has exactly one non-NA value and any number of NA. # 2. use the non-NA value in each partition (`max()` is just the simplest # way to do that reliable among databases). # 3. remove the helper column again. partition_sql <- purrr::map( cols_to_fill, ~ translate_sql( cumsum(ifelse(is.na(!!.x), 0L, 1L)), vars_order = translate_sql(!!!order_by_cols, con = .con), vars_group = op_grps(.data), ) ) %>% set_names(paste0("..dbplyr_partion_", seq_along(cols_to_fill))) dp <- .data %>% mutate(!!!partition_sql) fill_sql <- purrr::map2( cols_to_fill, names(partition_sql), ~ translate_sql( max(!!.x, na.rm = TRUE), con = .con, vars_group = c(op_grps(.data), .y), ) ) %>% set_names(purrr::map_chr(cols_to_fill, as_name)) dp %>% transmute( !!!syms(colnames(.data)), !!!fill_sql ) %>% select(!!!colnames(.data)) } #' @export dbplyr_fill0.PostgreSQL <- dbplyr_fill0.SQLiteConnection #' @export dbplyr_fill0.PqConnection <- dbplyr_fill0.SQLiteConnection #' @export dbplyr_fill0.HDB <- dbplyr_fill0.SQLiteConnection #' @export dbplyr_fill0.ACCESS <- dbplyr_fill0.SQLiteConnection #' @export dbplyr_fill0.MariaDBConnection <- dbplyr_fill0.SQLiteConnection #' @export dbplyr_fill0.MySQLConnection <- dbplyr_fill0.SQLiteConnection #' @export dbplyr_fill0.MySQL <- dbplyr_fill0.SQLiteConnection last_value_sql <- function(con, x) { UseMethod("last_value_sql") } #' @export last_value_sql.DBIConnection <- function(con, x) { build_sql("LAST_VALUE(", ident(as.character(x)), " IGNORE NULLS)", con = con) } #' @export last_value_sql.Hive <- function(con, x) { translate_sql(last_value(!!x, TRUE), con = con) } globalVariables("last_value") dbplyr/R/db-escape.R0000644000176200001440000000313114002647450013715 0ustar liggesusers#' SQL escaping/quoting generics #' #' These generics translate individual values into SQL. The core #' generics are [DBI::dbQuoteIdentifier()] and[DBI::dbQuoteString] #' for quoting identifiers and strings, but dbplyr needs additional #' tools for inserting logical, date, date-time, and raw values into #' queries. #' #' @keywords internal #' @family generic #' @name db-quote #' @aliases NULL #' @examples #' con <- simulate_dbi() #' sql_escape_logical(con, c(TRUE, FALSE, NA)) #' sql_escape_date(con, Sys.Date()) #' sql_escape_date(con, Sys.time()) #' sql_escape_raw(con, charToRaw("hi")) NULL #' @rdname db-quote #' @export sql_escape_logical <- function(con, x) { UseMethod("sql_escape_logical") } #' @export sql_escape_logical.DBIConnection <- function(con, x) { y <- as.character(x) y[is.na(x)] <- "NULL" y } #' @export #' @rdname db-quote sql_escape_date <- function(con, x) { UseMethod("sql_escape_date") } #' @export sql_escape_date.DBIConnection <- function(con, x) { sql_escape_string(con, as.character(x)) } #' @export #' @rdname db-quote sql_escape_datetime <- function(con, x) { UseMethod("sql_escape_datetime") } #' @export sql_escape_datetime.DBIConnection <- function(con, x) { x <- strftime(x, "%Y-%m-%dT%H:%M:%OSZ", tz = "UTC") sql_escape_string(con, x) } #' @export #' @rdname db-quote sql_escape_raw <- function(con, x) { UseMethod("sql_escape_raw") } #' @export sql_escape_raw.DBIConnection <- function(con, x) { # SQL-99 standard for BLOB literals # https://crate.io/docs/sql-99/en/latest/chapters/05.html#blob-literal-s paste0(c("X'", format(x), "'"), collapse = "") } dbplyr/R/backend-redshift.R0000644000176200001440000000547214006531646015304 0ustar liggesusers#' Backend: Redshift #' #' @description #' Base translations come from [PostgreSQL backend][simulate_postgres]. There #' are generally few differences, apart from string manipulation. #' #' Use `simulate_redshift()` with `lazy_frame()` to see simulated SQL without #' converting to live access database. #' #' @name backend-redshift #' @aliases NULL #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_redshift()) #' lf %>% transmute(x = paste(c, " times")) #' lf %>% transmute(x = substr(c, 2, 3)) #' lf %>% transmute(x = str_replace_all(c, "a", "z")) NULL #' @export #' @rdname backend-redshift simulate_redshift <- function() simulate_dbi("RedshiftConnection") #' @export dbplyr_edition.RedshiftConnection <- function(con) { 2L } #' @export dbplyr_edition.Redshift <- dbplyr_edition.RedshiftConnection #' @export sql_translation.RedshiftConnection <- function(con) { postgres <- sql_translation.PostgreSQL(con) sql_variant( sql_translator(.parent = postgres$scalar, # https://docs.aws.amazon.com/redshift/latest/dg/r_Numeric_types201.html#r_Numeric_types201-floating-point-types as.numeric = sql_cast("FLOAT"), as.double = sql_cast("FLOAT"), # https://stackoverflow.com/questions/56708136 paste = sql_paste_redshift(" "), paste0 = sql_paste_redshift(""), str_c = sql_paste_redshift(""), # https://docs.aws.amazon.com/redshift/latest/dg/r_SUBSTRING.html substr = sql_substr("SUBSTRING"), substring = sql_substr("SUBSTRING"), str_sub = sql_str_sub("SUBSTRING", "LEN"), # https://docs.aws.amazon.com/redshift/latest/dg/REGEXP_REPLACE.html str_replace = sql_not_supported("str_replace"), str_replace_all = function(string, pattern, replacement) { sql_expr(REGEXP_REPLACE(!!string, !!pattern, !!replacement)) } ), sql_translator(.parent = postgres$aggregate), sql_translator(.parent = postgres$window, # https://docs.aws.amazon.com/redshift/latest/dg/r_WF_LAG.html lag = function(x, n = 1L, order_by = NULL) { win_over( sql_expr(LAG(!!x, !!as.integer(n))), win_current_group(), order_by %||% win_current_order(), win_current_frame() ) }, # https://docs.aws.amazon.com/redshift/latest/dg/r_WF_LEAD.html lead = function(x, n = 1L, order_by = NULL) { win_over( sql_expr(LEAD(!!x, !!n)), win_current_group(), order_by %||% win_current_order(), win_current_frame() ) }, ) ) } #' @export sql_translation.Redshift <- sql_translation.RedshiftConnection sql_paste_redshift <- function(sep) { sql_paste_infix(sep, "||", function(x) sql_expr(cast(!!x %as% text))) } utils::globalVariables(c("REGEXP_REPLACE", "LAG", "LEAD")) dbplyr/R/memdb.R0000644000176200001440000000211014002647450013152 0ustar liggesusers#' Create a database table in temporary in-memory database. #' #' `memdb_frame()` works like [tibble::tibble()], but instead of creating a new #' data frame in R, it creates a table in [src_memdb()]. #' #' @inheritParams tibble::tibble #' @param name,.name Name of table in database: defaults to a random name that's #' unlikely to conflict with an existing table. #' @param df Data frame to copy #' @export #' @examples #' library(dplyr) #' df <- memdb_frame(x = runif(100), y = runif(100)) #' df %>% arrange(x) #' df %>% arrange(x) %>% show_query() #' #' mtcars_db <- tbl_memdb(mtcars) #' mtcars_db %>% group_by(cyl) %>% summarise(n = n()) %>% show_query() memdb_frame <- function(..., .name = unique_table_name()) { x <- copy_to(src_memdb(), tibble(...), name = .name) x } #' @rdname memdb_frame #' @export tbl_memdb <- function(df, name = deparse(substitute(df))) { copy_to(src_memdb(), df, name = name) } #' @rdname memdb_frame #' @export src_memdb <- function() { cache_computation("src_memdb", { src_dbi(DBI::dbConnect(RSQLite::SQLite(), ":memory:", create = TRUE)) }) } dbplyr/R/verb-do.R0000644000176200001440000001074314002647450013437 0ustar liggesusers#' Perform arbitrary computation on remote backend #' #' @inheritParams dplyr::do #' @param .chunk_size The size of each chunk to pull into R. If this number is #' too big, the process will be slow because R has to allocate and free a lot #' of memory. If it's too small, it will be slow, because of the overhead of #' talking to the database. #' @export #' @importFrom dplyr do do.tbl_sql <- function(.data, ..., .chunk_size = 1e4L) { groups_sym <- groups(.data) if (length(groups_sym) == 0) { .data <- collect(.data) return(do(.data, ...)) } args <- quos(...) named <- named_args(args) # Create data frame of labels labels <- .data %>% select(!!! groups_sym) %>% summarise() %>% collect() con <- .data$src$con n <- nrow(labels) m <- length(args) out <- replicate(m, vector("list", n), simplify = FALSE) names(out) <- names(args) p <- progress_estimated(n * m, min_time = 2) # Create ungrouped data frame suitable for chunked retrieval query <- Query$new(con, db_sql_render(con, ungroup(.data)), op_vars(.data)) # When retrieving in pages, there's no guarantee we'll get a complete group. # So we always assume the last group in the chunk is incomplete, and leave # it for the next. If the group size is large than chunk size, it may # take a couple of iterations to get the entire group, but that should # be an unusual situation. last_group <- NULL i <- 0 # Assumes `chunk` to be ordered with group columns first gvars <- seq_along(groups_sym) # Initialise a data mask for tidy evaluation env <- env(empty_env()) mask <- new_data_mask(env) query$fetch_paged(.chunk_size, function(chunk) { if (!is_null(last_group)) { chunk <- rbind(last_group, chunk) } # Create an id for each group grouped <- chunk %>% dplyr::group_by(!!! syms(names(chunk)[gvars])) if (utils::packageVersion("dplyr") < "0.7.9") { index <- attr(grouped, "indices") # convert from 0-index index <- lapply(index, `+`, 1L) } else { index <- dplyr::group_rows(grouped) } n <- length(index) last_group <<- chunk[index[[length(index)]], , drop = FALSE] for (j in seq_len(n - 1)) { cur_chunk <- chunk[index[[j]], , drop = FALSE] # Update pronouns within the data mask env$. <- cur_chunk env$.data <- cur_chunk for (k in seq_len(m)) { out[[k]][[i + j]] <<- eval_tidy(args[[k]], mask) p$tick()$print() } } i <<- i + (n - 1) }) # Process last group if (!is_null(last_group)) { env$. <- last_group last_group <- env$.data for (k in seq_len(m)) { out[[k]][[i + 1]] <- eval_tidy(args[[k]], mask) p$tick()$print() } } if (!named) { label_output_dataframe(labels, out, group_vars(.data)) } else { label_output_list(labels, out, group_vars(.data)) } } # Helper functions ------------------------------------------------------------- label_output_dataframe <- function(labels, out, groups) { data_frame <- vapply(out[[1]], is.data.frame, logical(1)) if (any(!data_frame)) { stop( "Results are not data frames at positions: ", paste(which(!data_frame), collapse = ", "), call. = FALSE ) } rows <- vapply(out[[1]], nrow, numeric(1)) out <- dplyr::bind_rows(out[[1]]) if (!is.null(labels)) { # Remove any common columns from labels labels <- labels[setdiff(names(labels), names(out))] # Repeat each row to match data labels <- labels[rep(seq_len(nrow(labels)), rows), , drop = FALSE] rownames(labels) <- NULL dplyr::grouped_df(dplyr::bind_cols(labels, out), groups) } else { dplyr::rowwise(out) } } label_output_list <- function(labels, out, groups) { if (!is.null(labels)) { labels[names(out)] <- out dplyr::rowwise(labels) } else { class(out) <- "data.frame" attr(out, "row.names") <- .set_row_names(length(out[[1]])) dplyr::rowwise(out) } } named_args <- function(args) { # Arguments must either be all named or all unnamed. named <- sum(names2(args) != "") if (!(named == 0 || named == length(args))) { stop( "Arguments to do() must either be all named or all unnamed", call. = FALSE ) } if (named == 0 && length(args) > 1) { stop("Can only supply single unnamed argument to do()", call. = FALSE) } # Check for old syntax if (named == 1 && names(args) == ".f") { stop( "do syntax changed in dplyr 0.2. Please see documentation for details", call. = FALSE ) } named != 0 } dbplyr/R/sql-expr.R0000644000176200001440000000437714002647450013662 0ustar liggesusers#' Generate SQL from R expressions #' #' Low-level building block for generating SQL from R expressions. #' Strings are escaped; names become bare SQL identifiers. User infix #' functions have `%` stripped. #' #' Using `sql_expr()` in package will require use of [globalVariables()] #' to avoid `R CMD check` NOTES. This is a small amount of additional pain, #' which I think is worthwhile because it leads to more readable translation #' code. #' #' @param x A quasiquoted expression #' @param con Connection to use for escaping. Will be set automatically when #' called from a function translation. #' @param .fn Function name (as string, call, or symbol) #' @param ... Arguments to function #' @keywords internal #' @export #' @examples #' con <- simulate_dbi() # not necessary when writing translations #' #' sql_expr(f(x + 1), con = con) #' sql_expr(f("x", "y"), con = con) #' sql_expr(f(x, y), con = con) #' #' x <- ident("x") #' sql_expr(f(!!x, y), con = con) #' #' sql_expr(cast("x" %as% DECIMAL), con = con) #' sql_expr(round(x) %::% numeric, con = con) #' #' sql_call2("+", quote(x), 1, con = con) #' sql_call2("+", "x", 1, con = con) sql_expr <- function(x, con = sql_current_con()) { x <- enexpr(x) x <- replace_expr(x, con = con) sql(x) } #' @export #' @rdname sql_expr sql_call2 <- function(.fn, ..., con = sql_current_con()) { fn <- call2(.fn, ...) fn <- replace_expr(fn, con = con) sql(fn) } replace_expr <- function(x, con) { if (is.atomic(x) || blob::is_blob(x)) { as.character(escape(unname(x), con = con)) } else if (is.name(x)) { as.character(x) # } else if (is.call(x) && identical(x[[1]], quote(I))) { # escape(ident(as.character(x[[2]]))) } else if (is.call(x)) { fun <- toupper(as.character(x[[1]])) args <- lapply(x[-1], replace_expr, con = con) if (is_infix_base(fun)) { if (length(args) == 1) { paste0(fun, args[[1]]) } else { paste0(args[[1]], " ", fun, " ", args[[2]]) } } else if (is_infix_user(fun)) { fun <- substr(fun, 2, nchar(fun) - 1) paste0(args[[1]], " ", fun, " ", args[[2]]) } else if (fun == "(") { paste0("(", paste0(args, collapse = ", "), ")") } else { paste0(fun, "(", paste0(args, collapse = ", "), ")") } } else { x } } dbplyr/R/data-cache.R0000644000176200001440000000270514002647450014052 0ustar liggesuserscache <- function() { if (!is_attached("dbplyr_cache")) { get("attach")(new_environment(), name = "dbplyr_cache", pos = length(search()) - 1) } search_env("dbplyr_cache") } cache_computation <- function(name, computation) { cache <- cache() if (env_has(cache, name)) { env_get(cache, name) } else { res <- force(computation) env_poke(cache, name, res) res } } load_srcs <- function(f, src_names, quiet = NULL) { if (is.null(quiet)) { quiet <- !identical(Sys.getenv("NOT_CRAN"), "true") } srcs <- lapply(src_names, function(x) { out <- NULL try(out <- f(x), silent = TRUE) if (is.null(out) && !quiet) { message("Could not instantiate ", x, " src") } out }) purrr::compact(setNames(srcs, src_names)) } db_location <- function(path = NULL, filename) { if (!is.null(path)) { # Check that path is a directory and is writeable if (!file.exists(path) || !file.info(path)$isdir) { stop(path, " is not a directory", call. = FALSE) } if (!is_writeable(path)) stop("Can not write to ", path, call. = FALSE) return(file.path(path, filename)) } pkg <- file.path(system.file("db", package = "dplyr")) if (is_writeable(pkg)) return(file.path(pkg, filename)) tmp <- tempdir() if (is_writeable(tmp)) return(file.path(tmp, filename)) stop("Could not find writeable location to cache db", call. = FALSE) } is_writeable <- function(x) { unname(file.access(x, 2) == 0) } dbplyr/R/verb-arrange.R0000644000176200001440000000357114002647450014455 0ustar liggesusers#' Arrange rows by column values #' #' @description #' This is an method for the dplyr [arrange()] generic. It generates #' the `ORDER BY` clause of the SQL query. It also affects the #' [window_order()] of windowed expressions in [mutate.tbl_lazy()]. #' #' Note that `ORDER BY` clauses can not generally appear in subqueries, which #' means that you should `arrange()` as late as possible in your pipelines. #' #' @section Missing values: #' Unlike R, most databases sorts `NA` (`NULL`s) at the front. You can #' can override this behaviour by explicitly sorting on `is.na(x)`. #' #' @param .data A lazy data frame backed by a database query. #' @inheritParams dplyr::arrange #' @return Another `tbl_lazy`. Use [show_query()] to see the generated #' query, and use [`collect()`][collect.tbl_sql] to execute the query #' and return data to R. #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' db <- memdb_frame(a = c(3, 4, 1, 2), b = c(5, 1, 2, NA)) #' db %>% arrange(a) %>% show_query() #' #' # Note that NAs are sorted first #' db %>% arrange(b) #' # override by sorting on is.na() first #' db %>% arrange(is.na(b), b) #' @export #' @importFrom dplyr arrange arrange.tbl_lazy <- function(.data, ..., .by_group = FALSE) { dots <- quos(...) dots <- partial_eval_dots(dots, vars = op_vars(.data)) names(dots) <- NULL add_op_single( "arrange", .data, dots = dots, args = list(.by_group = .by_group) ) } #' @export op_sort.op_arrange <- function(op) { c(op_sort(op$x), op$dots) } #' @export op_desc.op_arrange <- function(x, ...) { op_desc(x$x, ...) } #' @export sql_build.op_arrange <- function(op, con, ...) { order_vars <- translate_sql_(op$dots, con, context = list(clause = "ORDER")) if (op$args$.by_group) { order_vars <- c.sql(ident(op_grps(op$x)), order_vars, con = con) } select_query( sql_build(op$x, con), order_by = order_vars ) } dbplyr/R/backend-hive.R0000644000176200001440000000373314015731174014423 0ustar liggesusers#' Backend: Hive #' #' @description #' See `vignette("translate-function")` and `vignette("translate-verb")` for #' details of overall translation technology. Key differences for this backend #' are a scattering of custom translations provided by users. #' #' Use `simulate_hive()` with `lazy_frame()` to see simulated SQL without #' converting to live access database. #' #' @name backend-hive #' @aliases NULL #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' lf <- lazy_frame(a = TRUE, b = 1, d = 2, c = "z", con = simulate_hive()) #' lf %>% transmute(x = cot(b)) #' lf %>% transmute(x = bitwShiftL(c, 1L)) #' lf %>% transmute(x = str_replace_all(z, "a", "b")) #' #' lf %>% summarise(x = median(d, na.rm = TRUE)) #' lf %>% summarise(x = var(c, na.rm = TRUE)) NULL #' @export #' @rdname simulate_dbi simulate_hive <- function() simulate_dbi("Hive") #' @export dbplyr_edition.Hive <- function(con) { 2L } #' @export sql_translation.Hive <- function(con) { sql_variant( sql_translator(.parent = base_odbc_scalar, bitwShiftL = sql_prefix("SHIFTLEFT", 2), bitwShiftR = sql_prefix("SHIFTRIGHT", 2), cot = function(x){ sql_expr(1 / tan(!!x)) }, str_replace_all = function(string, pattern, replacement) { sql_expr(regexp_replace(!!string, !!pattern, !!replacement)) } ), sql_translator(.parent = base_odbc_agg, var = sql_aggregate("VARIANCE", "var"), quantile = sql_quantile("PERCENTILE"), median = sql_median("PERCENTILE") ), sql_translator(.parent = base_odbc_win, var = win_aggregate("VARIANCE"), quantile = sql_quantile("PERCENTILE", window = TRUE), median = sql_median("PERCENTILE", window = TRUE) ) ) } #' @export sql_table_analyze.Hive <- function(con, table, ...) { # https://cwiki.apache.org/confluence/display/Hive/StatsDev build_sql( "ANALYZE TABLE ", as.sql(table, con = con), " COMPUTE STATISTICS", con = con ) } globalVariables("regexp_replace") dbplyr/R/build-sql.R0000644000176200001440000000357113474056125014003 0ustar liggesusers#' Build a SQL string. #' #' This is a convenience function that should prevent sql injection attacks #' (which in the context of dplyr are most likely to be accidental not #' deliberate) by automatically escaping all expressions in the input, while #' treating bare strings as sql. This is unlikely to prevent any serious #' attack, but should make it unlikely that you produce invalid sql. #' #' This function should be used only when generating `SELECT` clauses, #' other high level queries, or for other syntax that has no R equivalent. #' For individual function translations, prefer [sql_expr()]. #' #' @param ... input to convert to SQL. Use [sql()] to preserve #' user input as is (dangerous), and [ident()] to label user #' input as sql identifiers (safe) #' @param .env the environment in which to evaluate the arguments. Should not #' be needed in typical use. #' @param con database connection; used to select correct quoting characters. #' @keywords internal #' @export #' @examples #' con <- simulate_dbi() #' build_sql("SELECT * FROM TABLE", con = con) #' x <- "TABLE" #' build_sql("SELECT * FROM ", x, con = con) #' build_sql("SELECT * FROM ", ident(x), con = con) #' build_sql("SELECT * FROM ", sql(x), con = con) #' #' # http://xkcd.com/327/ #' name <- "Robert'); DROP TABLE Students;--" #' build_sql("INSERT INTO Students (Name) VALUES (", name, ")", con = con) build_sql <- function(..., .env = parent.frame(), con = sql_current_con()) { if (is.null(con)) { stop("`con` must not be NULL", call. = FALSE) } escape_expr <- function(x, con) { # If it's a string, leave it as is if (is.character(x)) return(x) val <- eval_bare(x, .env) # Skip nulls, so you can use if statements like in paste if (is.null(val)) return("") escape(val, con = con) } pieces <- purrr::map_chr(enexprs(...), escape_expr, con = con) sql(paste0(pieces, collapse = "")) } dbplyr/R/translate-sql.R0000644000176200001440000001774614006531012014673 0ustar liggesusers#' Translate an expression to sql #' #' @section Base translation: #' The base translator, `base_sql`, provides custom mappings for for #' commonly used base functions including logical (`!`, `&`, `|`), #' arithmetic (`^`), and comparison (`!=`) operators, as well as common #' summary (`mean()`, `var()`) and manipulation functions. #' #' All other functions will be preserved as is. R's infix functions #' (e.g. `%like%`) will be converted to their SQL equivalents (e.g. `LIKE`). #' You can use this to access SQL string concatenation: `||` is mapped to #' `OR`, but `%||%` is mapped to `||`. To suppress this behaviour, and force #' errors immediately when dplyr doesn't know how to translate a function it #' encounters, using set the `dplyr.strict_sql` option to `TRUE`. #' #' You can also use [sql()] to insert a raw sql string. #' #' @section SQLite translation: #' The SQLite variant currently only adds one additional function: a mapping #' from `sd()` to the SQL aggregation function `STDEV`. #' #' @param ...,dots Expressions to translate. `translate_sql()` #' automatically quotes them for you. `translate_sql_()` expects #' a list of already quoted objects. #' @param con An optional database connection to control the details of #' the translation. The default, `NULL`, generates ANSI SQL. #' @param vars Deprecated. Now call [partial_eval()] directly. #' @param vars_group,vars_order,vars_frame Parameters used in the `OVER` #' expression of windowed functions. #' @param window Use `FALSE` to suppress generation of the `OVER` #' statement used for window functions. This is necessary when generating #' SQL for a grouped summary. #' @param context Use to carry information for special translation cases. For example, MS SQL needs a different conversion for is.na() in WHERE vs. SELECT clauses. Expects a list. #' @export #' @examples #' # Regular maths is translated in a very straightforward way #' translate_sql(x + 1) #' translate_sql(sin(x) + tan(y)) #' #' # Note that all variable names are escaped #' translate_sql(like == "x") #' # In ANSI SQL: "" quotes variable _names_, '' quotes strings #' #' # Logical operators are converted to their sql equivalents #' translate_sql(x < 5 & !(y >= 5)) #' # xor() doesn't have a direct SQL equivalent #' translate_sql(xor(x, y)) #' #' # If is translated into case when #' translate_sql(if (x > 5) "big" else "small") #' #' # Infix functions are passed onto SQL with % removed #' translate_sql(first %like% "Had%") #' translate_sql(first %is% NA) #' translate_sql(first %in% c("John", "Roger", "Robert")) #' #' # And be careful if you really want integers #' translate_sql(x == 1) #' translate_sql(x == 1L) #' #' # If you have an already quoted object, use translate_sql_: #' x <- quote(y + 1 / sin(t)) #' translate_sql_(list(x), con = simulate_dbi()) #' #' # Windowed translation -------------------------------------------- #' # Known window functions automatically get OVER() #' translate_sql(mpg > mean(mpg)) #' #' # Suppress this with window = FALSE #' translate_sql(mpg > mean(mpg), window = FALSE) #' #' # vars_group controls partition: #' translate_sql(mpg > mean(mpg), vars_group = "cyl") #' #' # and vars_order controls ordering for those functions that need it #' translate_sql(cumsum(mpg)) #' translate_sql(cumsum(mpg), vars_order = "mpg") translate_sql <- function(..., con = NULL, vars = character(), vars_group = NULL, vars_order = NULL, vars_frame = NULL, window = TRUE) { if (!missing(vars)) { abort("`vars` is deprecated. Please use partial_eval() directly.") } con <- con %||% sql_current_con() %||% simulate_dbi() translate_sql_( quos(...), con = con, vars_group = vars_group, vars_order = vars_order, vars_frame = vars_frame, window = window ) } #' @export #' @rdname translate_sql translate_sql_ <- function(dots, con = NULL, vars_group = NULL, vars_order = NULL, vars_frame = NULL, window = TRUE, context = list()) { if (length(dots) == 0) { return(sql()) } stopifnot(is.list(dots)) if (!any(have_name(dots))) { names(dots) <- NULL } old_con <- set_current_con(con) on.exit(set_current_con(old_con), add = TRUE) if (length(context) > 0) { local_context(context) } if (window) { old_group <- set_win_current_group(vars_group) on.exit(set_win_current_group(old_group), add = TRUE) old_order <- set_win_current_order(vars_order) on.exit(set_win_current_order(old_order), add = TRUE) old_frame <- set_win_current_frame(vars_frame) on.exit(set_win_current_frame(old_frame), add = TRUE) } variant <- dbplyr_sql_translation(con) pieces <- lapply(dots, function(x) { if (is_null(get_expr(x))) { NULL } else if (is_atomic(get_expr(x))) { escape(get_expr(x), con = con) } else { mask <- sql_data_mask(x, variant, con = con, window = window) escape(eval_tidy(x, mask), con = con) } }) sql(unlist(pieces)) } sql_data_mask <- function(expr, variant, con, window = FALSE, strict = getOption("dplyr.strict_sql", FALSE)) { stopifnot(is.sql_variant(variant)) # Default for unknown functions if (!strict) { unknown <- setdiff(all_calls(expr), names(variant)) top_env <- ceply(unknown, default_op, parent = empty_env(), env = get_env(expr)) } else { top_env <- child_env(NULL) } # Known R -> SQL functions special_calls <- copy_env(variant$scalar, parent = top_env) if (!window) { special_calls2 <- copy_env(variant$aggregate, parent = special_calls) } else { special_calls2 <- copy_env(variant$window, parent = special_calls) } special_calls2$`::` <- function(pkg, name) { pkg <- as.character(substitute(pkg)) name <- as.character(substitute(name)) if (!is_installed(pkg)) { abort(glue("There is no package called '{pkg}'")) } if (!env_has(ns_env(pkg), name)) { abort(glue("'{name}' is not an exported object from '{pkg}'")) } if (env_has(special_calls2, name) || env_has(special_calls, name)) { env_get(special_calls2, name, inherit = TRUE) } else { abort(glue("No known translation for {pkg}::{name}()")) } } # Existing symbols in expression names <- all_names(expr) idents <- lapply(names, ident) name_env <- ceply(idents, escape, con = con, parent = special_calls2) # Known sql expressions symbol_env <- env_clone(base_symbols, parent = name_env) new_data_mask(symbol_env, top_env) } is_infix_base <- function(x) { x %in% c("::", "$", "@", "^", "*", "/", "+", "-", ">", ">=", "<", "<=", "==", "!=", "!", "&", "&&", "|", "||", "~", "<-", "<<-") } is_infix_user <- function(x) { grepl("^%.*%$", x) } default_op <- function(x, env) { assert_that(is_string(x)) # Check for shiny reactives; these are zero-arg functions # so need special handling to give a useful error obj <- env_get(env, x, default = NULL, inherit = TRUE) if (inherits(obj, "reactive")) { error_embed("a shiny reactive", "foo()") } if (is_infix_base(x)) { sql_infix(x) } else if (is_infix_user(x)) { x <- substr(x, 2, nchar(x) - 1) sql_infix(x) } else { sql_prefix(x) } } all_calls <- function(x) { if (is_quosure(x)) return(all_calls(quo_get_expr(x))) if (!is.call(x)) return(NULL) fname <- as.character(x[[1]]) unique(c(fname, unlist(lapply(x[-1], all_calls), use.names = FALSE))) } all_names <- function(x) { if (is.name(x)) return(as.character(x)) if (is_quosure(x)) return(all_names(quo_get_expr(x))) if (!is.call(x)) return(NULL) unique(unlist(lapply(x[-1], all_names), use.names = FALSE)) } # character vector -> environment ceply <- function(x, f, ..., parent = parent.frame()) { if (length(x) == 0) return(new.env(parent = parent)) l <- lapply(x, f, ...) names(l) <- x list2env(l, parent = parent) } dbplyr/R/verb-compute.R0000644000176200001440000000502114003640520014472 0ustar liggesusers#' Compute results of a query #' #' These are methods for the dplyr generics [collapse()], [compute()], #' and [collect()]. `collapse()` creates a subquery, `compute()` stores #' the results in a remote table, and `collect()` executes the query and #' downloads the data into R. #' #' @export #' @param x A lazy data frame backed by a database query. #' @importFrom dplyr collapse #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' db <- memdb_frame(a = c(3, 4, 1, 2), b = c(5, 1, 2, NA)) #' db %>% filter(a <= 2) %>% collect() collapse.tbl_sql <- function(x, ...) { sql <- db_sql_render(x$src$con, x) tbl(x$src, sql) %>% group_by(!!! syms(op_grps(x))) %>% add_op_order(op_sort(x)) } # compute ----------------------------------------------------------------- #' @rdname collapse.tbl_sql #' @param name Table name in remote database. #' @param temporary Should the table be temporary (`TRUE`, the default`) or #' persistent (`FALSE`)? #' @inheritParams copy_to.src_sql #' @export #' @importFrom dplyr compute compute.tbl_sql <- function(x, name = unique_table_name(), temporary = TRUE, unique_indexes = list(), indexes = list(), analyze = TRUE, ...) { vars <- op_vars(x) assert_that(all(unlist(indexes) %in% vars)) assert_that(all(unlist(unique_indexes) %in% vars)) x_aliased <- select(x, !!! syms(vars)) # avoids problems with SQLite quoting (#1754) sql <- db_sql_render(x$src$con, x_aliased$ops) name <- db_compute(x$src$con, name, sql, temporary = temporary, unique_indexes = unique_indexes, indexes = indexes, analyze = analyze, ... ) tbl(x$src, name) %>% group_by(!!! syms(op_grps(x))) %>% add_op_order(op_sort(x)) } # collect ----------------------------------------------------------------- #' @rdname collapse.tbl_sql #' @param n Number of rows to fetch. Defaults to `Inf`, meaning all rows. #' @param warn_incomplete Warn if `n` is less than the number of result rows? #' @importFrom dplyr collect #' @export collect.tbl_sql <- function(x, ..., n = Inf, warn_incomplete = TRUE) { if (identical(n, Inf)) { n <- -1 } else { # Gives the query planner information that it might be able to take # advantage of x <- head(x, n) } sql <- db_sql_render(x$src$con, x) out <- db_collect(x$src$con, sql, n = n, warn_incomplete = warn_incomplete) dplyr::grouped_df(out, intersect(op_grps(x), names(out))) } dbplyr/R/verb-summarise.R0000644000176200001440000000736414006531012015035 0ustar liggesusers#' Summarise each group to one row #' #' This is a method for the dplyr [summarise()] generic. It generates the #' `SELECT` clause of the SQL query, and generally needs to be combined with #' `group_by()`. #' #' @inheritParams arrange.tbl_lazy #' @inheritParams dplyr::summarise #' @param .groups \Sexpr[results=rd]{lifecycle::badge("experimental")} Grouping structure of the result. #' #' * "drop_last": dropping the last level of grouping. This was the #' only supported option before version 1.0.0. #' * "drop": All levels of grouping are dropped. #' * "keep": Same grouping structure as `.data`. #' #' When `.groups` is not specified, it defaults to "drop_last". #' #' In addition, a message informs you of that choice, unless the result is ungrouped, #' the option "dplyr.summarise.inform" is set to `FALSE`, #' or when `summarise()` is called from a function in a package. #' @inherit arrange.tbl_lazy return #' @importFrom dplyr summarise #' @export #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' db <- memdb_frame(g = c(1, 1, 1, 2, 2), x = c(4, 3, 6, 9, 2)) #' db %>% #' summarise(n()) %>% #' show_query() #' #' db %>% #' group_by(g) %>% #' summarise(n()) %>% #' show_query() summarise.tbl_lazy <- function(.data, ..., .groups = NULL) { dots <- quos(..., .named = TRUE) dots <- partial_eval_dots(dots, vars = op_vars(.data)) check_summarise_vars(dots) check_groups(.groups) add_op_single( "summarise", .data, dots = dots, args = list(.groups = .groups, env_caller = caller_env()) ) } # For each expression, check if it uses any newly created variables check_summarise_vars <- function(dots) { for (i in seq_along(dots)) { used_vars <- all_names(get_expr(dots[[i]])) cur_vars <- names(dots)[seq_len(i - 1)] if (any(used_vars %in% cur_vars)) { stop( "`", names(dots)[[i]], "` refers to a variable created earlier in this summarise().\n", "Do you need an extra mutate() step?", call. = FALSE ) } } } check_groups <- function(.groups) { if (is_null(.groups)) { return() } if (.groups %in% c("drop_last", "drop", "keep")) { return() } abort(c( paste0( "`.groups` can't be ", as_label(.groups), if (.groups == "rowwise") " in dbplyr" ), i = 'Possible values are NULL (default), "drop_last", "drop", and "keep"' )) } #' @export op_vars.op_summarise <- function(op) { c(op_grps(op$x), names(op$dots)) } #' @export op_grps.op_summarise <- function(op) { grps <- op_grps(op$x) .groups <- op$args$.groups %||% "drop_last" switch(.groups, drop_last = grps[-length(grps)], keep = grps, drop = character() ) } #' @export op_sort.op_summarise <- function(op) NULL #' @export sql_build.op_summarise <- function(op, con, ...) { select_vars <- translate_sql_(op$dots, con, window = FALSE, context = list(clause = "SELECT")) group_vars <- op_grps(op$x) n <- length(group_vars) .groups <- op$args$.groups verbose <- summarise_verbose(.groups, op$args$env_caller) .groups <- .groups %||% "drop_last" if (verbose && n > 1) { new_groups <- glue::glue_collapse(paste0("'", group_vars[-n], "'"), sep = ", ") summarise_inform("has grouped output by {new_groups}") } group_vars <- c.sql(ident(group_vars), con = con) select_query( sql_build(op$x, con), select = c.sql(group_vars, select_vars, con = con), group_by = group_vars ) } summarise_verbose <- function(.groups, .env) { is.null(.groups) && is_reference(topenv(.env), global_env()) && !identical(getOption("dplyr.summarise.inform"), FALSE) } summarise_inform <- function(..., .env = parent.frame()) { inform(paste0( "`summarise()` ", glue(..., .envir = .env), '. You can override using the `.groups` argument.' )) } dbplyr/R/data-lahman.R0000644000176200001440000000541514002647450014250 0ustar liggesusers#' Cache and retrieve an `src_sqlite` of the Lahman baseball database. #' #' This creates an interesting database using data from the Lahman baseball #' data source, provided by Sean Lahman at #' \url{http://www.seanlahman.com/baseball-archive/statistics/}, and #' made easily available in R through the \pkg{Lahman} package by #' Michael Friendly, Dennis Murphy and Martin Monkman. See the documentation #' for that package for documentation of the individual tables. #' #' @param ... Other arguments passed to `src` on first #' load. For MySQL and PostgreSQL, the defaults assume you have a local #' server with `lahman` database already created. #' For `lahman_srcs()`, character vector of names giving srcs to generate. #' @param quiet if `TRUE`, suppress messages about databases failing to #' connect. #' @param type src type. #' @keywords internal #' @examples #' # Connect to a local sqlite database, if already created #' \donttest{ #' library(dplyr) #' #' if (has_lahman("sqlite")) { #' lahman_sqlite() #' batting <- tbl(lahman_sqlite(), "Batting") #' batting #' } #' #' # Connect to a local postgres database with lahman database, if available #' if (has_lahman("postgres")) { #' lahman_postgres() #' batting <- tbl(lahman_postgres(), "Batting") #' } #' } #' @name lahman NULL #' @export #' @rdname lahman lahman_sqlite <- function(path = NULL) { path <- db_location(path, "lahman.sqlite") con <- DBI::dbConnect(RSQLite::SQLite(), dbname = path) copy_lahman(con) } #' @export #' @rdname lahman lahman_postgres <- function(dbname = "lahman", host = "localhost", ...) { con <- DBI::dbConnect(RPostgres::Postgres(), dbname = dbname, host = host, ...) copy_lahman(con) } #' @export #' @rdname lahman lahman_mysql <- function(dbname = "lahman", ...) { con <- DBI::dbConnect(RMariaDB::MariaDB(), dbname = dbname, ...) copy_lahman(con) } #' @rdname lahman #' @export copy_lahman <- function(con, ...) { # Create missing tables tables <- setdiff(lahman_tables(), DBI::dbListTables(con)) for (table in tables) { df <- getExportedValue("Lahman", table) message("Creating table: ", table) ids <- as.list(names(df)[grepl("ID$", names(df))]) copy_to(con, df, table, indexes = ids, temporary = FALSE) } con } # Get list of all non-label data frames in package lahman_tables <- function() { tables <- utils::data(package = "Lahman")$results[, 3] tables[!grepl("Labels", tables)] } #' @rdname lahman #' @export has_lahman <- function(type, ...) { if (!requireNamespace("Lahman", quietly = TRUE)) return(FALSE) succeeds(lahman(type, ...), quiet = FALSE) } #' @rdname lahman #' @export lahman_srcs <- function(..., quiet = NULL) { load_srcs(lahman, c(...), quiet = quiet) } lahman <- function(type, ...) { f <- match.fun(paste0("lahman_", type)) f(...) } dbplyr/R/sql.R0000644000176200001440000000335714002647450012703 0ustar liggesusers#' SQL escaping. #' #' These functions are critical when writing functions that translate R #' functions to sql functions. Typically a conversion function should escape #' all its inputs and return an sql object. #' #' @param ... Character vectors that will be combined into a single SQL #' expression. #' @export sql <- function(...) { x <- c_character(...) structure(x, class = c("sql", "character")) } # See setOldClass definition in zzz.R # c() is also called outside of the dbplyr context so must supply default # connection - this seems like a design mistake, and probably an indication # that within dbplyr c() should be replace with a more specific function #' @export c.sql <- function(..., drop_null = FALSE, con = simulate_dbi()) { input <- list(...) if (drop_null) input <- purrr::compact(input) out <- unlist(lapply(input, escape, collapse = NULL, con = con)) sql(out) } #' @export c.ident <- c.sql #' @export unique.sql <- function(x, ...) { sql(NextMethod()) } #' @rdname sql #' @export is.sql <- function(x) inherits(x, "sql") #' @export print.sql <- function(x, ...) cat(format(x, ...), sep = "\n") #' @export format.sql <- function(x, ...) { if (length(x) == 0) { paste0(" [empty]") } else { if (!is.null(names(x))) { paste0(" ", paste0(x, " AS ", names(x))) } else { paste0(" ", x) } } } #' @rdname sql #' @export #' @param x Object to coerce #' @param con Needed when `x` is directly suppled from the user so that #' schema specifications can be quoted using the correct identifiers. as.sql <- function(x, con) UseMethod("as.sql") #' @export as.sql.ident <- function(x, con) x #' @export as.sql.sql <- function(x, con) x #' @export as.sql.character <- function(x, con) ident(x) dbplyr/R/translate-sql-conditional.R0000644000176200001440000000315614002647450017174 0ustar liggesuserssql_if <- function(cond, if_true, if_false = NULL) { build_sql( "CASE WHEN (", cond, ")", " THEN (", if_true, ")", if (!is.null(if_false)) build_sql(" WHEN NOT(", cond, ") THEN (", if_false, ")"), " END" ) } sql_case_when <- function(...) { # TODO: switch to dplyr::case_when_prepare when available formulas <- list2(...) n <- length(formulas) if (n == 0) { abort("No cases provided") } query <- vector("list", n) value <- vector("list", n) for (i in seq_len(n)) { f <- formulas[[i]] env <- environment(f) query[[i]] <- escape(eval_bare(f[[2]], env), con = sql_current_con()) value[[i]] <- escape(eval_bare(f[[3]], env), con = sql_current_con()) } clauses <- purrr::map2_chr(query, value, ~ paste0("WHEN (", .x, ") THEN (", .y, ")")) # if a formula like TRUE ~ "other" is at the end of a sequence, use ELSE statement if (query[[n]] == "TRUE") { clauses[[n]] <- paste0("ELSE (", value[[n]], ")") } sql(paste0( "CASE\n", paste0(clauses, collapse = "\n"), "\nEND" )) } sql_switch <- function(x, ...) { input <- list2(...) named <- names(input) != "" clauses <- purrr::map2_chr(names(input)[named], input[named], function(x, y) { build_sql("WHEN (", x , ") THEN (", y, ") ") }) n_unnamed <- sum(!named) if (n_unnamed == 0) { # do nothing } else if (n_unnamed == 1) { clauses <- c(clauses, build_sql("ELSE ", input[!named], " ")) } else { stop("Can only have one unnamed (ELSE) input", call. = FALSE) } build_sql("CASE ", x, " ", !!!clauses, "END") } sql_is_null <- function(x) { sql_expr((((!!x)) %is% NULL)) } dbplyr/R/verb-count.R0000644000176200001440000000275314006531012014155 0ustar liggesusers#' Count observations by group #' #' These are methods for the dplyr [count()] and [tally()] generics. They #' wrap up [group_by.tbl_lazy()], [summarise.tbl_lazy()] and, optionally, #' [arrange.tbl_lazy()]. #' #' @inheritParams arrange.tbl_lazy #' @inheritParams dplyr::count #' @importFrom dplyr count #' @export #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' db <- memdb_frame(g = c(1, 1, 1, 2, 2), x = c(4, 3, 6, 9, 2)) #' db %>% count(g) %>% show_query() #' db %>% count(g, wt = x) %>% show_query() #' db %>% count(g, wt = x, sort = TRUE) %>% show_query() count.tbl_lazy <- function(x, ..., wt = NULL, sort = FALSE, name = NULL) { if (!missing(...)) { out <- group_by(x, ..., .add = TRUE) } else { out <- x } out <- tally(out, wt = {{ wt }}, sort = sort, name = name) out <- group_by(out, !!!syms(group_vars(x))) out } #' @rdname count.tbl_lazy #' @importFrom dplyr tally #' @export tally.tbl_lazy <- function(x, wt = NULL, sort = FALSE, name = NULL) { wt <- enquo(wt) if (quo_is_null(wt)) { n <- expr(n()) } else { n <- expr(sum(!!wt, na.rm = TRUE)) } name <- check_name(name, group_vars(x)) out <- summarise(x, !!name := !!n, .groups = "drop") if (sort) { arrange(out, desc(!!sym(name))) } else { out } } check_name <- function(name, vars) { name <- name %||% "n" if (!name %in% vars) { return(name) } abort(c( glue("'{name}' already present in output"), i = "Use `name = \"new_name\"` to pick a new name." )) } dbplyr/R/verb-head.R0000644000176200001440000000310114002647450013724 0ustar liggesusers#' Subset the first rows #' #' @description #' This is a method for the [head()] generic. It is usually translated to the #' `LIMIT` clause of the SQL query. Because `LIMIT` is not an official part of #' the SQL specification, some database use other clauses like `TOP` or #' `FETCH ROWS`. #' #' Note that databases don't really have a sense of row order, so what "first" #' means is subject to interpretation. Most databases will respect ordering #' performed with `arrange()`, but it's not guaranteed. `tail()` is not #' supported at all because the situation is even murkier for the "last" rows. #' #' @param x A lazy data frame backed by a database query. #' @param n Number of rows to return #' @param ... Not used. #' @inherit arrange.tbl_lazy return #' @export #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' db <- memdb_frame(x = 1:100) #' db %>% head() %>% show_query() #' #' # Pretend we have data in a SQL server database #' db2 <- lazy_frame(x = 1:100, con = simulate_mssql()) #' db2 %>% head() %>% show_query() head.tbl_lazy <- function(x, n = 6L, ...) { if (!is.numeric(n) || length(n) != 1L || n < 0) { abort("`n` must be a non-negative integer") } n <- trunc(n) if (inherits(x$ops, "op_head")) { x$ops$args$n <- min(x$ops$args$n, n) } else { x$ops <- op_single("head", x = x$ops, args = list(n = n)) } x } #' @export tail.tbl_lazy <- function(x, n = 6L, ...) { stop("tail() is not supported by sql sources", call. = FALSE) } #' @export sql_build.op_head <- function(op, con, ...) { select_query(sql_build(op$x, con), limit = op$args$n) } dbplyr/R/backend-postgres.R0000644000176200001440000001727114004012136015324 0ustar liggesusers#' Backend: PostgreSQL #' #' @description #' See `vignette("translate-function")` and `vignette("translate-verb")` for #' details of overall translation technology. Key differences for this backend #' are: #' #' * Many stringr functions #' * lubridate date-time extraction functions #' * More standard statistical summaries #' #' Use `simulate_postgres()` with `lazy_frame()` to see simulated SQL without #' converting to live access database. #' #' @name backend-postgres #' @aliases NULL #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_postgres()) #' lf %>% summarise(x = sd(b, na.rm = TRUE)) #' lf %>% summarise(y = cor(b, c), y = cov(b, c)) NULL #' @export #' @rdname backend-postgres simulate_postgres <- function() simulate_dbi("PqConnection") #' @export dbplyr_edition.PostgreSQL <- function(con) { 2L } #' @export dbplyr_edition.PqConnection <- dbplyr_edition.PostgreSQL #' @export db_connection_describe.PqConnection <- function(con) { info <- dbGetInfo(con) host <- if (info$host == "") "localhost" else info$host paste0("postgres ", info$serverVersion, " [", info$username, "@", host, ":", info$port, "/", info$dbname, "]") } #' @export db_connection_describe.PostgreSQL <- db_connection_describe.PqConnection postgres_grepl <- function(pattern, x, ignore.case = FALSE, perl = FALSE, fixed = FALSE, useBytes = FALSE) { # https://www.postgresql.org/docs/current/static/functions-matching.html#FUNCTIONS-POSIX-TABLE if (any(c(perl, fixed, useBytes))) { abort("`perl`, `fixed` and `useBytes` parameters are unsupported") } if (ignore.case) { sql_expr(((!!x)) %~*% ((!!pattern))) } else { sql_expr(((!!x)) %~% ((!!pattern))) } } postgres_round <- function(x, digits = 0L) { digits <- as.integer(digits) sql_expr(round(((!!x)) %::% numeric, !!digits)) } #' @export sql_translation.PqConnection <- function(con) { sql_variant( sql_translator(.parent = base_scalar, bitwXor = sql_infix("#"), log10 = function(x) sql_expr(log(!!x)), log = sql_log(), cot = sql_cot(), round = postgres_round, grepl = postgres_grepl, paste = sql_paste(" "), paste0 = sql_paste(""), # stringr functions # https://www.postgresql.org/docs/9.1/functions-string.html # https://www.postgresql.org/docs/9.1/functions-matching.html#FUNCTIONS-POSIX-REGEXP str_c = sql_paste(""), str_locate = function(string, pattern) { sql_expr(strpos(!!string, !!pattern)) }, str_detect = function(string, pattern, negate = FALSE) { if (isTRUE(negate)) { sql_expr(!(!!string ~ !!pattern)) } else { sql_expr(!!string ~ !!pattern) } }, str_replace = function(string, pattern, replacement){ sql_expr(regexp_replace(!!string, !!pattern, !!replacement)) }, str_replace_all = function(string, pattern, replacement){ sql_expr(regexp_replace(!!string, !!pattern, !!replacement, 'g')) }, str_squish = function(string){ sql_expr(ltrim(rtrim(regexp_replace(!!string, '\\s+', ' ', 'g')))) }, str_remove = function(string, pattern){ sql_expr(regexp_replace(!!string, !!pattern, '')) }, str_remove_all = function(string, pattern){ sql_expr(regexp_replace(!!string, !!pattern, '', 'g')) }, # lubridate functions month = function(x, label = FALSE, abbr = TRUE) { if (!label) { sql_expr(EXTRACT(MONTH %FROM% !!x)) } else { if (abbr) { sql_expr(TO_CHAR(!!x, "Mon")) } else { sql_expr(TO_CHAR(!!x, "Month")) } } }, quarter = function(x, with_year = FALSE, fiscal_start = 1) { if (fiscal_start != 1) { stop("`fiscal_start` is not supported in PostgreSQL translation. Must be 1.", call. = FALSE) } if (with_year) { sql_expr((EXTRACT(YEAR %FROM% !!x) || '.' || EXTRACT(QUARTER %FROM% !!x))) } else { sql_expr(EXTRACT(QUARTER %FROM% !!x)) } }, wday = function(x, label = FALSE, abbr = TRUE, week_start = NULL) { if (!label) { week_start <- week_start %||% getOption("lubridate.week.start", 7) offset <- as.integer(7 - week_start) sql_expr(EXTRACT("dow" %FROM% DATE(!!x) + !!offset) + 1) } else if (label && !abbr) { sql_expr(TO_CHAR(!!x, "Day")) } else if (label && abbr) { sql_expr(SUBSTR(TO_CHAR(!!x, "Day"), 1, 3)) } else { stop("Unrecognized arguments to `wday`", call. = FALSE) } }, yday = function(x) sql_expr(EXTRACT(DOY %FROM% !!x)), # https://www.postgresql.org/docs/13/datatype-datetime.html#DATATYPE-INTERVAL-INPUT seconds = function(x) { interval <- paste(x, "seconds") sql_expr(CAST(!!interval %AS% INTERVAL)) }, minutes = function(x) { interval <- paste(x, "minutes") sql_expr(CAST(!!interval %AS% INTERVAL)) }, hours = function(x) { interval <- paste(x, "hours") sql_expr(CAST(!!interval %AS% INTERVAL)) }, days = function(x) { interval <- paste(x, "days") sql_expr(CAST(!!interval %AS% INTERVAL)) }, weeks = function(x) { interval <- paste(x, "weeks") sql_expr(CAST(!!interval %AS% INTERVAL)) }, months = function(x) { interval <- paste(x, "months") sql_expr(CAST(!!interval %AS% INTERVAL)) }, years = function(x) { interval <- paste(x, "years") sql_expr(CAST(!!interval %AS% INTERVAL)) }, # https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC floor_date = function(x, unit = "seconds") { unit <- arg_match(unit, c("second", "minute", "hour", "day", "week", "month", "quarter", "year") ) sql_expr(DATE_TRUNC(!!unit, !!x)) }, ), sql_translator(.parent = base_agg, cor = sql_aggregate_2("CORR"), cov = sql_aggregate_2("COVAR_SAMP"), sd = sql_aggregate("STDDEV_SAMP", "sd"), var = sql_aggregate("VAR_SAMP", "var"), all = sql_aggregate("BOOL_AND", "all"), any = sql_aggregate("BOOL_OR", "any"), str_flatten = function(x, collapse) sql_expr(string_agg(!!x, !!collapse)) ), sql_translator(.parent = base_win, cor = win_aggregate_2("CORR"), cov = win_aggregate_2("COVAR_SAMP"), sd = win_aggregate("STDDEV_SAMP"), var = win_aggregate("VAR_SAMP"), all = win_aggregate("BOOL_AND"), any = win_aggregate("BOOL_OR"), str_flatten = function(x, collapse) { win_over( sql_expr(string_agg(!!x, !!collapse)), partition = win_current_group(), order = win_current_order() ) } ) ) } #' @export sql_translation.PostgreSQL <- sql_translation.PqConnection #' @export sql_expr_matches.PqConnection <- function(con, x, y) { # https://www.postgresql.org/docs/current/functions-comparison.html build_sql(x, " IS NOT DISTINCT FROM ", y, con = con) } #' @export sql_expr_matches.PostgreSQL <- sql_expr_matches.PqConnection # http://www.postgresql.org/docs/9.3/static/sql-explain.html #' @export sql_query_explain.PqConnection <- function(con, sql, format = "text", ...) { format <- match.arg(format, c("text", "json", "yaml", "xml")) build_sql( "EXPLAIN ", if (!is.null(format)) sql(paste0("(FORMAT ", format, ") ")), sql, con = con ) } #' @export sql_query_explain.PostgreSQL <- sql_query_explain.PqConnection globalVariables(c("strpos", "%::%", "%FROM%", "DATE", "EXTRACT", "TO_CHAR", "string_agg", "%~*%", "%~%", "MONTH", "DOY", "DATE_TRUNC", "INTERVAL")) dbplyr/R/testthat.R0000644000176200001440000000224013415745770013745 0ustar liggesusers expect_equal_tbl <- function(object, expected, ..., info = NULL, label = NULL, expected.label = NULL) { lab_act <- label %||% expr_label(substitute(object)) lab_exp <- expected.label %||% expr_label(substitute(expected)) ok <- dplyr::all_equal(collect(object), collect(expected), ...) msg <- glue(" {lab_act} not equal to {lab_exp}. {paste(ok, collapse = '\n')} ") testthat::expect(isTRUE(ok), msg, info = info) } expect_equal_tbls <- function(results, ref = NULL, ...) { stopifnot(is.list(results)) if (!is_named(results)) { result_name <- expr_name(substitute(results)) names(results) <- paste0(result_name, "_", seq_along(results)) } # If ref is NULL, use the first result if (is.null(ref)) { if (length(results) < 2) { testthat::skip("Need at least two srcs to compare") } ref <- results[[1]] ref_name <- names(results)[[1]] rest <- results[-1] } else { rest <- results ref_name <- "`ref`" } for (i in seq_along(rest)) { expect_equal_tbl( rest[[i]], ref, ..., label = names(rest)[[i]], expected.label = ref_name ) } invisible(TRUE) } dbplyr/R/verb-window.R0000644000176200001440000000331614002647450014342 0ustar liggesusers#' Override window order and frame #' #' These allow you to override the `PARTITION BY` and `ORDER BY` clauses #' of window functions generated by grouped mutates. #' #' @inheritParams arrange.tbl_lazy #' @param ... Variables to order by #' @export #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' db <- memdb_frame(g = rep(1:2, each = 5), y = runif(10), z = 1:10) #' db %>% #' window_order(y) %>% #' mutate(z = cumsum(y)) %>% #' show_query() #' #' db %>% #' group_by(g) %>% #' window_frame(-3, 0) %>% #' window_order(z) %>% #' mutate(z = sum(x)) %>% #' show_query() window_order <- function(.data, ...) { dots <- quos(...) dots <- partial_eval_dots(dots, vars = op_vars(.data)) names(dots) <- NULL add_op_order(.data, dots) } # We want to preserve this ordering (for window functions) without # imposing an additional arrange, so we have a special op_order add_op_order <- function(.data, dots = list()) { if (length(dots) == 0) { return(.data) } .data$ops <- op_single("order", x = .data$ops, dots = dots) .data } #' @export op_sort.op_order <- function(op) { op$dots } #' @export sql_build.op_order <- function(op, con, ...) { sql_build(op$x, con, ...) } # Frame ------------------------------------------------------------------- #' @export #' @rdname window_order #' @param from,to Bounds of the frame. window_frame <- function(.data, from = -Inf, to = Inf) { stopifnot(is.numeric(from), length(from) == 1) stopifnot(is.numeric(to), length(to) == 1) add_op_single("frame", .data, args = list(range = c(from, to))) } #' @export op_frame.op_frame <- function(op) { op$args$range } #' @export sql_build.op_frame <- function(op, con, ...) { sql_build(op$x, con, ...) } dbplyr/R/tbl-lazy.R0000644000176200001440000000354114002647450013635 0ustar liggesusers#' Create a local lazy tibble #' #' These functions are useful for testing SQL generation without having to #' have an active database connection. See [simulate_dbi()] for a list #' available database simulations. #' #' @keywords internal #' @export #' @examples #' library(dplyr) #' df <- data.frame(x = 1, y = 2) #' #' df_sqlite <- tbl_lazy(df, con = simulate_sqlite()) #' df_sqlite %>% summarise(x = sd(x, na.rm = TRUE)) %>% show_query() tbl_lazy <- function(df, con = NULL, src = NULL) { if (!is.null(src)) { warn("`src` is deprecated; please use `con` instead") con <- src } con <- con %||% sql_current_con() %||% simulate_dbi() subclass <- class(con)[[1]] dplyr::make_tbl( purrr::compact(c(subclass, "lazy")), ops = op_base_local(df), src = src_dbi(con) ) } setOldClass(c("tbl_lazy", "tbl")) #' @export #' @rdname tbl_lazy lazy_frame <- function(..., con = NULL, src = NULL) { con <- con %||% sql_current_con() %||% simulate_dbi() tbl_lazy(tibble(...), con = con, src = src) } #' @export dimnames.tbl_lazy <- function(x) { list(NULL, op_vars(x$ops)) } #' @export dim.tbl_lazy <- function(x) { c(NA, length(op_vars(x$ops))) } #' @export print.tbl_lazy <- function(x, ...) { show_query(x) } #' @export as.data.frame.tbl_lazy <- function(x, row.names, optional, ...) { stop("Can not coerce `tbl_lazy` to data.frame", call. = FALSE) } #' @importFrom dplyr same_src #' @export same_src.tbl_lazy <- function(x, y) { inherits(y, "tbl_lazy") } #' @importFrom dplyr tbl_vars #' @export tbl_vars.tbl_lazy <- function(x) { op_vars(x$ops) } #' @importFrom dplyr groups #' @export groups.tbl_lazy <- function(x) { lapply(group_vars(x), as.name) } # Manually registered in zzz.R group_by_drop_default.tbl_lazy <- function(x) { TRUE } #' @importFrom dplyr group_vars #' @export group_vars.tbl_lazy <- function(x) { op_grps(x$ops) } dbplyr/R/translate-sql-string.R0000644000176200001440000000420214006567571016201 0ustar liggesusers# R prefers to specify start / stop or start / end # databases usually specify start / length # https://www.postgresql.org/docs/current/functions-string.html #' @export #' @rdname sql_variant sql_substr <- function(f = "SUBSTR") { function(x, start, stop) { start <- max(check_integer(start, "start"), 1L) stop <- max(check_integer(stop, "stop"), 1L) length <- max(stop - start + 1L, 0L) sql_call2(f, x, start, length) } } check_integer <- function(x, arg) { if (length(x) != 1 || !is.numeric(x)) { abort(paste0("`", arg, "` must be a single number")) } as.integer(x) } # str_sub(x, start, end) - start and end can be negative # SUBSTR(string, start, length) - start can be negative #' @export #' @rdname sql_variant sql_str_sub <- function( subset_f = "SUBSTR", length_f = "LENGTH", optional_length = TRUE ) { function(string, start = 1L, end = -1L) { stopifnot(length(start) == 1L, length(end) == 1L) start <- check_integer(start, "start") end <- check_integer(end, "stop") start_sql <- start_pos(string, start, length_f) if (optional_length && end == -1L) { sql_call2(subset_f, string, start_sql) } else { if (end == 0L) { length_sql <- 0L } else if(start > 0 && end < 0) { n <- start - end - 2L if (n == 0) { length_sql <- sql_call2(length_f, string) } else { length_sql <- sql_expr(!!sql_call2(length_f, string) - !!n) } } else { length_sql <- pmax(end - start + 1L, 0L) } sql_call2(subset_f, string, start_sql, length_sql) } } } start_pos <- function(string, start, length_f) { if (start == -1) { sql_call2(length_f, string) } else if (start < 0) { sql_expr(!!sql_call2(length_f, string) - !!abs(start + 1L)) } else { start } } sql_str_trim <- function(string, side = c("both", "left", "right")) { side <- match.arg(side) switch(side, left = sql_expr(ltrim(!!string)), right = sql_expr(rtrim(!!string)), both = sql_expr(ltrim(rtrim(!!string))), ) } globalVariables(c("ltrim", "rtrim")) dbplyr/R/db.R0000644000176200001440000000424614002647450012467 0ustar liggesusers#' Miscellaneous database generics #' #' * `db_connection_describe()` provides a short string describing the #' database connection, helping users tell which database a table comes #' from. It should be a single line, and ideally less than 60 characters wide. #' #' * `dbplyr_edition()` declares which version of the dbplyr API you want. #' See below for more details. #' #' @section dplyr 2.0.0: #' dplyr 2.0.0 renamed a number of generics so that they could be cleanly moved #' from dplyr to dbplyr. If you have an existing backend, you'll need to rename #' the following methods. #' #' * `dplyr::db_desc()` -> `dbplyr::db_connection_describe()` (also note that #' the argument named changed from `x` to `con`). #' #' @family generic #' @keywords internal #' @name db-misc #' @aliases NULL NULL dbplyr_connection_describe <- function(con, ...) { dbplyr_fallback(con, "db_desc", ...) } #' @export #' @importFrom dplyr db_desc db_desc.DBIConnection <- function(x) { db_connection_describe(x) } #' @export #' @rdname db-misc db_connection_describe <- function(con) { UseMethod("db_connection_describe") } #' @export db_connection_describe.DBIConnection <- function(con) { class(con)[[1]] } #' @rdname db-misc #' @export sql_join_suffix <- function(con, ...) { UseMethod("sql_join_suffix") } #' @export sql_join_suffix.DBIConnection <- function(con, ...) { c(".x", ".y") } #' @rdname db-misc #' @export db_sql_render <- function(con, sql, ...) { UseMethod("db_sql_render") } #' @export db_sql_render.DBIConnection <- function(con, sql, ...) { sql_render(sql, con = con, ...) } #' @rdname db-misc #' @export dbplyr_edition <- function(con) { UseMethod("dbplyr_edition") } #' @export dbplyr_edition.default <- function(con) { 1L } # Needed because pool uses an object of call Pool/R6 # fallback helper --------------------------------------------------------- dbplyr_fallback <- function(con, .generic, ...) { if (dbplyr_edition(con) >= 2) { # Always call DBIConnection method which contains the default implementation fun <- sym(paste0(.generic, ".DBIConnection")) } else { fun <- call("::", quote(dplyr), sym(.generic)) } eval_bare(expr((!!fun)(con, ...))) } dbplyr/R/verb-pull.R0000644000176200001440000000130414004012136013767 0ustar liggesusers#' Extract a single column #' #' This is a method for the dplyr [pull()] generic. It evaluates the query #' retrieving just the specified column. #' #' @inheritParams arrange.tbl_lazy #' @inheritParams dplyr::pull #' @return A vector of data. #' @importFrom dplyr pull #' @export #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' db <- memdb_frame(x = 1:5, y = 5:1) #' db %>% #' mutate(z = x + y * 2) %>% #' pull() pull.tbl_sql <- function(.data, var = -1) { vars <- tbl_vars(.data) if (length(vars) > 1 || !missing(var)) { var <- tidyselect::vars_pull(vars, {{ var }}) .data <- ungroup(.data) .data <- select(.data, !! sym(var)) } .data <- collect(.data) .data[[1]] } dbplyr/R/backend-oracle.R0000644000176200001440000001206114015731174014727 0ustar liggesusers#' Backend: Oracle #' #' @description #' See `vignette("translate-function")` and `vignette("translate-verb")` for #' details of overall translation technology. Key differences for this backend #' are: #' #' * Use `FETCH FIRST` instead of `LIMIT` #' * Custom types #' * `paste()` uses `||` #' * Custom subquery generation (no `AS`) #' * `setdiff()` uses `MINUS` instead of `EXCEPT` #' #' Use `simulate_oracle()` with `lazy_frame()` to see simulated SQL without #' converting to live access database. #' #' @name backend-oracle #' @aliases NULL #' @examples #' library(dplyr, warn.conflicts = FALSE) #' #' lf <- lazy_frame(a = TRUE, b = 1, c = 2, d = "z", con = simulate_oracle()) #' lf %>% transmute(x = paste0(c, " times")) #' lf %>% setdiff(lf) NULL #' @export #' @rdname backend-oracle simulate_oracle <- function() simulate_dbi("Oracle") #' @export dbplyr_edition.Oracle <- function(con) { 2L } #' @export sql_query_select.Oracle <- function(con, select, from, where = NULL, group_by = NULL, having = NULL, order_by = NULL, limit = NULL, distinct = FALSE, ..., subquery = FALSE) { sql_select_clauses(con, select = sql_clause_select(con, select, distinct), from = sql_clause_from(con, from), where = sql_clause_where(con, where), group_by = sql_clause_group_by(con, group_by), having = sql_clause_having(con, having), order_by = sql_clause_order_by(con, order_by, subquery, limit), # Requires Oracle 12c, released in 2013 limit = if (!is.null(limit)) { build_sql("FETCH FIRST ", as.integer(limit), " ROWS ONLY", con = con) } ) } #' @export sql_translation.Oracle <- function(con) { sql_variant( sql_translator(.parent = base_odbc_scalar, # Data type conversions are mostly based on this article # https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements001.htm # https://stackoverflow.com/questions/1171196 as.character = sql_cast("VARCHAR2(255)"), # https://docs.oracle.com/cd/E17952_01/mysql-5.7-en/date-and-time-functions.html#function_date as.Date = function(x) sql_expr(DATE(!!x)), # bit64::as.integer64 can translate to BIGINT for some # vendors, which is equivalent to NUMBER(19) in Oracle # https://docs.oracle.com/cd/B19306_01/gateways.102/b14270/apa.htm as.integer64 = sql_cast("NUMBER(19)"), as.numeric = sql_cast("NUMBER"), as.double = sql_cast("NUMBER"), # string ----------------------------------------------------------------- # https://docs.oracle.com/cd/B19306_01/server.102/b14200/operators003.htm#i997789 paste = sql_paste_infix(" ", "||", function(x) sql_expr(cast(!!x %as% text))), paste0 = sql_paste_infix("", "||", function(x) sql_expr(cast(!!x %as% text))), # lubridate -------------------------------------------------------------- today = function() sql_expr(TRUNC(CURRENT_TIMESTAMP)), now = function() sql_expr(CURRENT_TIMESTAMP) ), base_odbc_agg, base_odbc_win ) } #' @export sql_query_explain.Oracle <- function(con, sql, ...) { build_sql( "EXPLAIN PLAN FOR ", sql, ";\n", "SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY()));", con = con ) } #' @export sql_table_analyze.Oracle <- function(con, table, ...) { # https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_4005.htm build_sql("ANALYZE TABLE ", as.sql(table, con = con), " COMPUTE STATISTICS", con = con) } #' @export sql_query_wrap.Oracle <- function(con, from, name = unique_subquery_name(), ...) { # Table aliases in Oracle should not have an "AS": https://www.techonthenet.com/oracle/alias.php if (is.ident(from)) { build_sql("(", from, ") ", if (!is.null(name)) ident(name), con = con) } else { build_sql("(", from, ") ", ident(name %||% unique_subquery_name()), con = con) } } # registered onLoad located in the zzz.R script setdiff.tbl_Oracle <- function(x, y, copy = FALSE, ...) { # Oracle uses MINUS instead of EXCEPT for this operation: # https://docs.oracle.com/cd/B19306_01/server.102/b14200/queries004.htm add_op_set_op(x, y, "MINUS", copy = copy, ...) } #' @export sql_expr_matches.Oracle <- function(con, x, y) { # https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions040.htm build_sql("decode(", x, ", ", y, ", 0, 1) = 0", con = con) } # roacle package ---------------------------------------------------------- #' @export dbplyr_edition.OraConnection <- dbplyr_edition.Oracle #' @export sql_translation.OraConnection <- sql_translation.Oracle #' @export sql_query_select.OraConnection <- sql_query_select.Oracle #' @export sql_table_analyze.OraConnection <- sql_table_analyze.Oracle #' @export sql_query_wrap.OraConnection <- sql_query_wrap.Oracle # registered onLoad located in the zzz.R script setdiff.OraConnection <- setdiff.tbl_Oracle #' @export sql_expr_matches.OraConnection <- sql_expr_matches.Oracle globalVariables(c("DATE", "CURRENT_TIMESTAMP", "TRUNC")) dbplyr/R/schema.R0000644000176200001440000000361214015731174013337 0ustar liggesusers#' Refer to a table in a schema #' #' @param schema,table Names of schema and table. These will be automatically #' quoted; use `sql()` to pass a raw name that won't get quoted. #' @export #' @examples #' in_schema("my_schema", "my_table") #' # eliminate quotes #' in_schema(sql("my_schema"), sql("my_table")) #' #' # Example using schemas with SQLite #' con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") #' #' # Add auxilary schema #' tmp <- tempfile() #' DBI::dbExecute(con, paste0("ATTACH '", tmp, "' AS aux")) #' #' library(dplyr, warn.conflicts = FALSE) #' copy_to(con, iris, "df", temporary = FALSE) #' copy_to(con, mtcars, in_schema("aux", "df"), temporary = FALSE) #' #' con %>% tbl("df") #' con %>% tbl(in_schema("aux", "df")) in_schema <- function(schema, table) { structure( list(schema = as.sql(schema), table = as.sql(table)), class = "dbplyr_schema" ) } #' @export print.dbplyr_schema <- function(x, ...) { cat_line(" ", escape_ansi(x$schema), ".", escape_ansi(x$table)) } #' @export as.sql.dbplyr_schema <- function(x, con) { ident_q(paste0(escape(x$schema, con = con), ".", escape(x$table, con = con))) } is.schema <- function(x) inherits(x, "dbplyr_schema") # Support for DBI::Id() --------------------------------------------------- #' @export as.sql.Id <- function(x, con) ident_q(dbQuoteIdentifier(con, x)) # Old dbplyr approach ----------------------------------------------------- #' Declare a identifer as being pre-quoted. #' #' No longer needed; please use [sql()] instead. #' #' @keywords internal #' @export ident_q <- function(...) { x <- c_character(...) structure(x, class = c("ident_q", "ident", "character")) } #' @export escape.ident_q <- function(x, parens = FALSE, collapse = ", ", con = NULL) { sql_vector(names_to_as(x, names2(x), con = con), parens, collapse, con = con) } #' @export dbi_quote.ident_q <- function(x, con) DBI::SQL(as.character(x)) dbplyr/R/lazy-ops.R0000644000176200001440000001045014002647450013652 0ustar liggesusers#' Lazy operations #' #' This set of S3 classes describe the action of dplyr verbs. These are #' currently used for SQL sources to separate the description of operations #' in R from their computation in SQL. This API is very new so is likely #' to evolve in the future. #' #' `op_vars()` and `op_grps()` compute the variables and groups from #' a sequence of lazy operations. `op_sort()` and `op_frame()` tracks the #' order and frame for use in window functions. #' #' @keywords internal #' @name lazy_ops NULL # Base constructors ------------------------------------------------------- #' @export #' @rdname lazy_ops op_base <- function(x, vars, class = character()) { stopifnot(is.character(vars)) structure( list( x = x, vars = vars ), class = c(paste0("op_base_", class), "op_base", "op") ) } op_base_local <- function(df) { op_base(df, names(df), class = "local") } op_base_remote <- function(x, vars) { op_base(x, vars, class = "remote") } #' @export print.op_base_remote <- function(x, ...) { if (inherits(x$x, "ident")) { cat("From: ", x$x, "\n", sep = "") } else { cat("From: \n") } cat("\n", sep = "") } #' @export print.op_base_local <- function(x, ...) { cat(" ", dplyr::dim_desc(x$x), "\n", sep = "") } #' @export sql_build.op_base_remote <- function(op, con, ...) { op$x } #' @export sql_build.op_base_local <- function(op, con, ...) { ident("df") } # Operators --------------------------------------------------------------- #' @export #' @rdname lazy_ops op_single <- function(name, x, dots = list(), args = list()) { structure( list( name = name, x = x, dots = dots, args = args ), class = c(paste0("op_", name), "op_single", "op") ) } #' @export #' @rdname lazy_ops add_op_single <- function(name, .data, dots = list(), args = list()) { .data$ops <- op_single(name, x = .data$ops, dots = dots, args = args) .data } #' @export print.op_single <- function(x, ...) { print(x$x) cat("-> ", x$name, "()\n", sep = "") for (dot in x$dots) { cat(" - ", deparse_trunc(dot), "\n", sep = "") } } #' @export #' @rdname lazy_ops op_double <- function(name, x, y, args = list()) { structure( list( name = name, x = x, y = y, args = args ), class = c(paste0("op_", name), "op_double", "op") ) } # op_grps ----------------------------------------------------------------- #' @export #' @rdname lazy_ops op_grps <- function(op) UseMethod("op_grps") #' @export op_grps.op_base <- function(op) character() #' @export op_grps.op_single <- function(op) op_grps(op$x) #' @export op_grps.op_double <- function(op) op_grps(op$x) #' @export op_grps.tbl_lazy <- function(op) op_grps(op$ops) # op_vars ----------------------------------------------------------------- #' @export #' @rdname lazy_ops op_vars <- function(op) UseMethod("op_vars") #' @export op_vars.op_base <- function(op) op$vars #' @export op_vars.op_single <- function(op) op_vars(op$x) #' @export op_vars.op_double <- function(op) stop("Not implemented", call. = FALSE) #' @export op_vars.tbl_lazy <- function(op) op_vars(op$ops) # op_sort ----------------------------------------------------------------- #' @export #' @rdname lazy_ops op_sort <- function(op) UseMethod("op_sort") #' @export op_sort.op_base <- function(op) NULL #' @export op_sort.op_single <- function(op) op_sort(op$x) #' @export op_sort.op_double <- function(op) op_sort(op$x) #' @export op_sort.tbl_lazy <- function(op) op_sort(op$ops) # op_frame ---------------------------------------------------------------- #' @export #' @rdname lazy_ops op_frame <- function(op) UseMethod("op_frame") #' @export op_frame.op_base <- function(op) NULL #' @export op_frame.op_single <- function(op) op_frame(op$x) #' @export op_frame.op_double <- function(op) op_frame(op$x) #' @export op_frame.tbl_lazy <- function(op) op_frame(op$ops) # Description ------------------------------------------------------------- op_rows <- function(op) "??" op_cols <- function(op) length(op_vars(op)) op_desc <- function(op) UseMethod("op_desc") #' @export op_desc.op <- function(x, ..., con = con) { "lazy query" } #' @export op_desc.op_base_remote <- function(op) { if (is.ident(op$x)) { paste0("table<", op$x, ">") } else { "SQL" } } dbplyr/R/query-semi-join.R0000644000176200001440000000174214002647450015135 0ustar liggesusers#' @export #' @rdname sql_build semi_join_query <- function(x, y, anti = FALSE, by = NULL, na_matches = FALSE) { structure( list( x = x, y = y, anti = anti, by = by, na_matches = na_matches ), class = c("semi_join_query", "query") ) } #' @export print.semi_join_query <- function(x, ...) { cat_line("") cat_line("By:") cat_line(indent(paste0(x$by$x, "-", x$by$y))) cat_line("X:") cat_line(indent_print(sql_build(x$x))) cat_line("Y:") cat_line(indent_print(sql_build(x$y))) } #' @export sql_render.semi_join_query <- function(query, con = NULL, ..., subquery = FALSE) { from_x <- dbplyr_sql_subquery( con, sql_render(query$x, con, ..., subquery = TRUE), name = "LHS" ) from_y <- dbplyr_sql_subquery( con, sql_render(query$y, con, ..., subquery = TRUE), name = "RHS" ) dbplyr_query_semi_join(con, from_x, from_y, anti = query$anti, by = query$by) } dbplyr/R/remote.R0000644000176200001440000000237514002647450013376 0ustar liggesusers#' Metadata about a remote table #' #' `remote_name()` gives the name remote table, or `NULL` if it's a query. #' `remote_query()` gives the text of the query, and `remote_query_plan()` #' the query plan (as computed by the remote database). `remote_src()` and #' `remote_con()` give the dplyr source and DBI connection respectively. #' #' @param x Remote table, currently must be a [tbl_sql]. #' @return The value, or `NULL` if not remote table, or not applicable. #' For example, computed queries do not have a "name" #' @export #' @examples #' mf <- memdb_frame(x = 1:5, y = 5:1, .name = "blorp") #' remote_name(mf) #' remote_src(mf) #' remote_con(mf) #' remote_query(mf) #' #' mf2 <- dplyr::filter(mf, x > 3) #' remote_name(mf2) #' remote_src(mf2) #' remote_con(mf2) #' remote_query(mf2) remote_name <- function(x) { if (!inherits(x$ops, "op_base")) return() x$ops$x } #' @export #' @rdname remote_name remote_src <- function(x) { x$src } #' @export #' @rdname remote_name remote_con <- function(x) { x$src$con } #' @export #' @rdname remote_name remote_query <- function(x) { db_sql_render(remote_con(x), x) } #' @export #' @rdname remote_name remote_query_plan <- function(x) { dbplyr_explain(remote_con(x), db_sql_render(remote_con(x), x$ops)) } dbplyr/R/sql-clause.R0000644000176200001440000000435714002647450014156 0ustar liggesuserssql_select_clauses <- function(con, select, from, where, group_by, having, order_by, limit = NULL) { out <- list( select = select, from = from, where = where, group_by = group_by, having = having, order_by = order_by, limit = limit ) out <- purrr::compact(out) escape(unname(out), collapse = "\n", parens = FALSE, con = con) } sql_clause_select <- function(con, select, distinct = FALSE, top = NULL) { assert_that(is.character(select)) if (is_empty(select)) { abort("Query contains no columns") } build_sql( "SELECT ", if (distinct) sql("DISTINCT "), if (!is.null(top)) build_sql("TOP ", as.integer(top), " ", con = con), escape(select, collapse = ", ", con = con), con = con ) } sql_clause_from <- function(con, from) { sql_clause_generic(con, "FROM", from) } sql_clause_where <- function(con, where) { if (length(where) == 0L) { return() } assert_that(is.character(where)) where_paren <- escape(where, parens = TRUE, con = con) build_sql( "WHERE ", sql_vector(where_paren, collapse = " AND ", con = con), con = con ) } sql_clause_group_by <- function(con, group_by) { sql_clause_generic(con, "GROUP BY", group_by) } sql_clause_having <- function(con, having) { sql_clause_generic(con, "HAVING", having) } sql_clause_order_by <- function(con, order_by, subquery = FALSE, limit = NULL) { if (subquery && length(order_by) > 0 && is.null(limit)) { warn_drop_order_by() NULL } else { sql_clause_generic(con, "ORDER BY", order_by) } } sql_clause_limit <- function(con, limit){ if (!is.null(limit) && !identical(limit, Inf)) { build_sql( "LIMIT ", sql(format(limit, scientific = FALSE)), con = con ) } } # helpers ----------------------------------------------------------------- sql_clause_generic <- function(con, clause, fields) { if (length(fields) == 0L) { return() } assert_that(is.character(fields)) build_sql( sql(clause), " ", escape(fields, collapse = ", ", con = con), con = con ) } dbplyr/NEWS.md0000644000176200001440000011160614033043015012642 0ustar liggesusers# dbplyr 2.1.1 * New support for Snowflake (@edgararuiz) * `compute()`, `sql_table_index()`, and `sql_query_wrap()` now work with schemas (@mgirlich, #595). * `if_any()` and `if_all()` are now translated. * `group_by()` now ungroups when the dots argument is empty and `.add` is `FALSE` (@mgirlich, #615). * `sql_escape_date()` and `sql_escape_datetime` gain methods for MS Access (@erikvona, #608). # dbplyr 2.1.0 ## New features * Thanks to @mgirlich, dbplyr gains support for key verbs from tidyr: `pivot_longer()` (#532), `pivot_wider()` (#543), `expand()` (#538), `complete()` (#538), `replace_na()` (#538), `fill()` (#566). * @mgirlich is now a dbplyr author in recognition of his significant and sustained contributions. * `across()` implementation has been rewritten to support more inputs: it now translates formulas (#525), works with SQL functions that don't have R translations (#534), and work with `NULL` (#554) * `summarise()` now supports argument `.groups` (@mgirlich, #584). ## SQL translation * All backends: `str_sub()`, `substr()` and `substring()` get better translations (#577). Most importantly, the results of using negative locations should match the underlying R implementations more closely. * MS SQL: * `as.integer()` and `as.integer64()` translations cast first to `NUMERIC` to avoid CASTing weirdness (@DavidPatShuiFong, #496). * Assumes a boolean context inside of `[` (#546) * `str_sub()` with `end = -1` now works (#577). * Redshift: `lag()` and `lead()` lose the `default` parameter since it's not supported (@hdplsa, #548). * SQLite: custom translation of `full_join()` and `right_join()` (@mgirlich, #536). ## Minor improvements and bug fixes * RPostgreSQL backend warns if `temporary = TRUE` since temporary tables are not supported by `RPostgreSQL::dbWriteTable()` (#574). * `count()` method provides closer match to dplyr semantics (#347). * `distinct()` now respects grouping (@mgirlich, #535). * `db_connection_describe()` no longer uses partial matching (@mgirlich, #564). * `pull()` no longer `select()`s the result when there's already only one variable (#562). * `select()` no longer relocates grouping variables to the front (@mgirlich, #568). and informs when adding missing grouping variables (@mgirlich, #559). * `tbl.src_dbi(...)` now passed on to `tbl_sql()` (#530). # dbplyr 2.0.0 ## dplyr 1.0.0 compatibility * `across()` is now translated into individual SQL statements (#480). * `rename()` and `select()` support dplyr 1.0.0 tidyselect syntax (apart from predicate functions which can't easily work on computed queries) (#502). * `relocate()` makes it easy to move columns (#494) and `rename_with()` makes it easy to rename columns programmatically (#502). * `slice_min()`, `slice_max()`, and `slice_order()` are now supported. `slice_head()` and `slice_tail()` throw clear error messages (#394) ## SQL generation * Documentation has been radically improved with new topics for each major verb and each backend giving more details about the SQL translation. * `intersect()`, `union()` and `setdiff()` gain an `all` argument to add the `ALL` argument (#414). * Join functions gains a `na_matches` argument that allows you to control whether or not `NA` (`NULL`) values match other `NA` values. The default is `"never"`, which is the usual behaviour in databases. You can set `na_matches = "na"` to match R's usual join behaviour (#180). Additional arguments error (instead of being silently swallowed) (#382). * Joins now only use aliases where needed to disambiguate columns; this should make generated queries more readable. * Subqueries no longer include an `ORDER BY` clause. This is not part of the SQL spec, and has very limited support across databases. Now such queries generate a warning suggesting that you move your `arrange()` call later in the pipeline (#276). (There's one exception: `ORDER BY` is still generated if `LIMIT` is present; this tends to affect the returns rows but not necessarily their order). * Subquery names are now scoped within the query. This makes query text deterministic which helps some query optimisers/cachers (#336). * `sql_optimise()` now can partially optimise a pipeline; due to an unfortunate bug it previously gave up too easily. * `in_schema()` quotes each input individually (#287) (use `sql()` to opt out of quoting, if needed). And `DBI::Id()` should work anywhere that `in_schema()` does. ## SQL translation * Experimental new SAP HANA backend (#233). Requires the latest version of odbc. * All backends: * You can now use `::` in translations, so that (e.g.) `dbplyr::n()` is translated to `count(*)` (#207). * `[[` can now also translate numeric indices (#520). * `%/%` now generates a clear error message; previously it was translated to `/` which is not correct (#108). * `n()` is translated to `count(*)` instead of `count()` (#343). * `sub_str()` translation is more consistent in edge cases (@ianmcook). * All `median()` (@lorenzwalthert, #483), `pmin()`, `pmax()` (#479), `sd()` and `var()` functions have an `na.rm` argument that warns once when not `TRUE`. This makes them consistent with `mean()` and `sum()`. * `substring()` is now translated the same way as `substr()` (#378). * [blob](https://blob.tidyverse.org/) vectors can now be used with `!!` and `!!!` operators, for example in `filter()` (@okhoma, #433) * MySQL uses standard SQL for index creation. * MS SQL translation does better a distinguishing between bit and boolean (#377, #318). `if` and `ifelse` once again generate `IIF`, creating simpler expressions. `as.*()` function uses `TRY_CAST()` instead of `CAST()` for version 11+ (2012+) (@DavidPatShuiFong, #380). * odbc no longer translates `count()`; this was an accidental inclusion. * Oracle translation now depends on Oracle 12c, and uses a "row-limiting" clause for `head()`. It gains translations for `today()` and `now()`, and improved `as.Date()` translation (@rlh1994, #267). * PostgreSQL: new translations for lubridate period functions `years()`, `months()`, `days()`, and `floor_date()` (@bkkkk, #333) and stringr functions `str_squish()`, `str_remove()`, and `str_remove_all()` (@shosaco). * New RedShift translations when used with `RPostgres::Redshift()`. * `str_replace()` errors since there's no Redshift translation, and `str_replace_all()` uses `REGEXP_REPLACE()` (#446). * `paste()` and `paste0()` use `||` (#458). * `as.numeric()` and `as.double()` cast to `FLOAT` (#408). * `substr()` and `str_sub()` use `SUBSTRING()` (#327). * SQLite gains translations for lubridate functions `today()`, `now()`, `year()`, `month()`, `day()`, `hour()`, `minute()`, `second()`,`yday()` (#262), and correct translation for `median()` (#357). ## Extensibility If you are the author of a dbplyr backend, please see `vignette("backend-2")` for details. * New `dbplyr_edition()` generic allows you to opt-in to the 2nd edition of the dbplyr API. * `db_write_table()` now calls `DBI::dbWriteTable()` instead of nine generics that formerly each did a small part: `db_create_indexes()`, `db_begin()`, `db_rollback()`, `db_commit()`, `db_list_tables()`, `drop_drop_table()`, `db_has_table()`, `db_create_table()`, and `db_data_types()`. You can now delete the methods for these generics. `db_query_rows()` is no longer used; it appears that it hasn't been used for some time, so if you have a method, you can delete it. * `DBI::dbQuoteIdentifier()` is now used instead of `sql_escape_ident()` and `DBI::dbQuoteString()` instead of `sql_escape_string()`. * A number of `db_*` generics have been replaced with new SQL generation generics: * `dplyr::db_analyze()` -> `dbplyr::sql_table_analyze()` * `dplyr::db_create_index()` -> `dbplyr::sql_table_index()` * `dplyr::db_explain()` -> `dbplyr::sql_queriy_explain()` * `dplyr::db_query_fields()` -> `dbplyr::sql_query_fields()` * `dplyr::db_save_query()` -> `dbplyr::sql_query_save()` This makes them easier to test and is an important part of the process of moving all database generics in dbplyr (#284). * A number of other generics have been renamed to facilitate the move from dplyr to dbplyr: * `dplyr::sql_select()` -> `dbplyr::sql_query_select()` * `dplyr::sql_join()` -> `dbplyr::sql_query_join()` * `dplyr::sql_semi_join()` -> `dbplyr::sql_query_semi_join()` * `dplyr::sql_set_op()` -> `dbplyr::sql_query_set_op()` * `dplyr::sql_subquery()` -> `dbplyr::sql_query_wrap()` * `dplyr::db_desc()` -> `dbplyr::db_connection_describe()` * New `db_temporary_table()` generic makes it easier to work with databases that require temporary tables to be specially named. * New `sql_expr_matches()` generic allows databases to use more efficient alternatives when determine if two values "match" (i.e. like equality but a pair of `NULL`s will also match). For more details, see * New `sql_join_suffix()` allows backends to control the default suffixes used (#254). ## Minor improvements and bug fixes * All old lazy eval shims have been removed. These have been deprecated for some time. * Date-time escaping methods for Athena and Presto have moved to the packages where they belong. * Attempting to embed a Shiny reactive in a query now gives a helpful error (#439). * `copy_lahman()` and `copy_nycflights13()` (and hence `nycflights13_sqlite()`) and friends now return DBI connections rather than the now deprecated `src_dbi()` (#440). * `copy_to()` can now `overwrite` when table is specified with schema (#489), and gains an `in_transaction` argument used to optionally suppress the transaction wrapper (#368). * `distinct()` no longer duplicates column if grouped (#354). * `transmute()` now correctly tracks variables it needs when creating subqueries (#313). * `mutate()` grouping variables no longer generates a downstream error (#396) * `mutate()` correctly generates subqueries when you re-use the same variable three or more times (#412). * `window_order()` overrides ordering, rather than appending to it. # dbplyr 1.4.4 * Internally `DBI::dbExecute()` now uses `immediate = TRUE`; this improves support for session-scoped temporary tables in MS SQL (@krlmlr, #438). * Subqueries with `ORDER BY` use `TOP 9223372036854775807` instead of `TOP 100 PERCENT` on SQL Server for compatibility with Azure Data Warehouse (#337, @alexkyllo). * `escape()` now supports `blob` vectors using new `sql_escape_raw()` generic. It enables using [blob](https://blob.tidyverse.org/) variables in dplyr verbs, for example to filter nvarchar values by UTF-16 blobs (see https://github.com/r-dbi/DBI/issues/215#issuecomment-356376133). (@okhoma, #433) * Added `setOldClass()` calls for `"ident"` and `"ident_q"` classes for compatibility with dplyr 1.0.0 (#448, @krlmlr). * Postgres `str_detect()` translation uses same argument names as stringr, and gains a `negate` argument (#444). * `semi_join()` and `anti_join()` now correctly support the `sql_on` argument (#443, @krlmlr). # dbplyr 1.4.3 * dbplyr now uses RPostgres (instead of RPostgreSQL) and RMariaDB (instead of RMySQL) for its internal tests and data functions (#427). * The Date and POSIXt methods for `escape()` now use exported `sql_escape_date()` and `sql_escape_datetime()` generics to allow backend specific formatting of date and datetime literals. These are used to provide methods for Athena and Presto backends (@OssiLehtinen, #384, #391). * `first()`, `last()`, `nth()`, `lead()` and `lag()` now respect the `window_frame()` (@krlmlr, #366). * SQL server: new translations for `str_flatten()` (@PauloJhonny, #405). * SQL server: temporary datasets are now session-local, not global (#401). * Postgres: correct `str_detect()`, `str_replace()` and `str_replace_all()` translation (@shosaco, #362). # dbplyr 1.4.2 * Fix bug when partially evaluating unquoting quosure containing a single symbol (#317) * Fixes for rlang and dpylr compatibility. # dbplyr 1.4.1 Minor improvements to SQL generation * `x %in% y` strips names of `y` (#269). * Enhancements for scoped verbs (`mutate_all()`, `summarise_if()`, `filter_at()` etc) (#296, #306). * MS SQL use `TOP 100 PERCENT` as stop-gap to allow subqueries with `ORDER BY` (#277). * Window functions now translated correctly for Hive (#293, @cderv). # dbplyr 1.4.0 ## Breaking changes * ``Error: `con` must not be NULL``: If you see this error, it probably means that you have forgotten to pass `con` down to a dbplyr function. Previously, dbplyr defaulted to using `simulate_dbi()` which introduced subtle escaping bugs. (It's also possible I have forgotten to pass it somewhere that the dbplyr tests don't pick up, so if you can't figure it out, please let me know). * Subsetting (`[[`, `$`, and `[`) functions are no longer evaluated locally. This makes the translation more consistent and enables useful new idioms for modern databases (#200). ## New features * MySQL/MariaDB (https://mariadb.com/kb/en/library/window-functions/) and SQLite (https://www.sqlite.org/windowfunctions.html) translations gain support for window functions, available in Maria DB 10.2, MySQL 8.0, and SQLite 3.25 (#191). * Overall, dplyr generates many fewer subqueries: * Joins and semi-joins no longer add an unneeded subquery (#236). This is facilitated by the new `bare_identifier_ok` argument to `sql_render()`; the previous argument was called `root` and confused me. * Many sequences of `select()`, `rename()`, `mutate()`, and `transmute()` can be collapsed into a single query, instead of always generating a subquery (#213). * New `vignette("sql")` describes some advantages of dbplyr over SQL (#205) and gives some advice about writing literal SQL inside of dplyr, when you need to (#196). * New `vignette("reprex")` gives some hints on creating reprexes that work anywhere (#117). This is supported by a new `tbl_memdb()` that matches the existing `tbl_lazy()`. * All `..._join()` functions gain an `sql_on` argument that allows specifying arbitrary join predicates in SQL code (#146, @krlmlr). ## SQL translations * New translations for some lubridate functions: `today()`, `now()`, `year()`, `month()`, `day()`, `hour()`, `minute()`, `second()`, `quarter()`, `yday()` (@colearendt, @derekmorr). Also added new translation for `as.POSIXct()`. * New translations for stringr functions: `str_c()`, `str_sub()`, `str_length()`, `str_to_upper()`, `str_to_lower()`, and `str_to_title()` (@colearendt). Non-translated stringr functions throw a clear error. * New translations for bitwise operations: `bitwNot()`, `bitwAnd()`, `bitwOr()`, `bitwXor()`, `bitwShiftL()`, and `bitwShiftR()`. Unlike the base R functions, the translations do not coerce arguments to integers (@davidchall, #235). * New translation for `x[y]` to `CASE WHEN y THEN x END`. This enables `sum(a[b == 0])` to work as you expect from R (#202). `y` needs to be a logical expression; if not you will likely get a type error from your database. * New translations for `x$y` and `x[["y"]]` to `x.y`, enabling you to index into nested fields in databases that provide them (#158). * The `.data` and `.env` pronouns of tidy evaluation are correctly translated (#132). * New translation for `median()` and `quantile()`. Works for all ANSI compliant databases (SQL Server, Postgres, MariaDB, Teradata) and has custom translations for Hive. Thanks to @edavidaja for researching the SQL variants! (#169) * `na_if()` is correct translated to `NULLIF()` (rather than `NULL_IF`) (#211). * `n_distinct()` translation throws an error when given more than one argument. (#101, #133). * New default translations for `paste()`, `paste0()`, and the hyperbolic functions (these previously were only available for ODBC databases). * Corrected translations of `pmin()` and `pmax()` to `LEAST()` and `GREATEST()` for ANSI compliant databases (#118), to `MIN()` and `MAX()` for SQLite, and to an error for SQL server. * New translation for `switch()` to the simple form of `CASE WHEN` (#192). ### SQL simulation SQL simulation makes it possible to see what dbplyr will translate SQL to, without having an active database connection, and is used for testing and generating reprexes. * SQL simulation has been overhauled. It now works reliably, is better documented, and always uses ANSI escaping (i.e. `` ` `` for field names and `'` for strings). * `tbl_lazy()` now actually puts a `dbplyr::src` in the `$src` field. This shouldn't affect any downstream code unless you were previously working around this weird difference between `tbl_lazy` and `tbl_sql` classes. It also includes the `src` class in its class, and when printed, shows the generated SQL (#111). ## Database specific improvements * MySQL/MariaDB * Translations also applied to connections via the odbc package (@colearendt, #238) * Basic support for regular expressions via `str_detect()` and `str_replace_all()` (@colearendt, #168). * Improved translation for `as.logical(x)` to `IF(x, TRUE, FALSE)`. * Oracle * New custom translation for `paste()` and `paste0()` (@cderv, #221) * Postgres * Basic support for regular expressions via `str_detect()` and `str_replace_all()` (@colearendt, #168). * SQLite * `explain()` translation now generates `EXPLAIN QUERY PLAN` which generates a higher-level, more human friendly explanation. * SQL server * Improved translation for `as.logical(x)` to `CAST(x as BIT)` (#250). * Translates `paste()`, `paste0()`, and `str_c()` to `+`. * `copy_to()` method applies temporary table name transformation earlier so that you can now overwrite temporary tables (#258). * `db_write_table()` method uses correct argument name for passing along field types (#251). ## Minor improvements and bug fixes * Aggregation functions only warn once per session about the use of `na.rm = TRUE` (#216). * table names generated by `random_table_name()` have the prefix "dbplyr_", which makes it easier to find them programmatically (@mattle24, #111) * Functions that are only available in a windowed (`mutate()`) query now throw an error when called in a aggregate (`summarise()`) query (#129) * `arrange()` understands the `.by_group` argument, making it possible sort by groups if desired. The default is `FALSE` (#115) * `distinct()` now handles computed variables like `distinct(df, y = x + y)` (#154). * `escape()`, `sql_expr()` and `build_sql()` no longer accept `con = NULL` as a shortcut for `con = simulate_dbi()`. This made it too easy to forget to pass `con` along, introducing extremely subtle escaping bugs. `win_over()` gains a `con` argument for the same reason. * New `escape_ansi()` always uses ANSI SQL 92 standard escaping (for use in examples and documentation). * `mutate(df, x = NULL)` drops `x` from the output, just like when working with local data frames (#194). * `partial_eval()` processes inlined functions (including rlang lambda functions). This makes dbplyr work with more forms of scoped verbs like `df %>% summarise_all(~ mean(.))`, `df %>% summarise_all(list(mean))` (#134). * `sql_aggregate()` now takes an optional argument `f_r` for passing to `check_na_rm()`. This allows the warning to show the R function name rather than the SQL function name (@sverchkov, #153). * `sql_infix()` gains a `pad` argument for the rare operator that doesn't need to be surrounded by spaces. * `sql_prefix()` no longer turns SQL functions into uppercase, allowing for correct translation of case-sensitive SQL functions (#181, @mtoto). * `summarise()` gives a clear error message if you refer to a variable created in that same `summarise()` (#114). * New `sql_call2()` which is to `rlang::call2()` as `sql_expr()` is to `rlang::expr()`. * `show_query()` and `explain()` use `cat()` rather than message. * `union()`, `union_all()`, `setdiff()` and `intersect()` do a better job of matching columns across backends (#183). # dbplyr 1.3.0 * Now supports for dplyr 0.8.0 (#190) and R 3.1.0 ## API changes * Calls of the form `dplyr::foo()` are now evaluated in the database, rather than locally (#197). * `vars` argument to `tbl_sql()` has been formally deprecated; it hasn't actually done anything for a while (#3254). * `src` and `tbl` objects now include a class generated from the class of the underlying connection object. This makes it possible for dplyr backends to implement different behaviour at the dplyr level, when needed. (#2293) ## SQL translation * `x %in% y` is now translated to `FALSE` if `y` is empty (@mgirlich, #160). * New `as.integer64(x)` translation to `CAST(x AS BIGINT)` (#3305) * `case_when` now translates with a ELSE clause if a formula of the form `TRUE~` is provided . (@cderv, #112) * `cummean()` now generates `AVG()` not `MEAN()` (#157) * `str_detect()` now uses correct parameter order (#3397) * MS SQL * Cumulative summary functions now work (#157) * `ifelse()` uses `CASE WHEN` instead of `IIF`; this allows more complex operations, such as `%in%`, to work properly (#93) * Oracle * Custom `db_drop_table()` now only drops tables if they exist (#3306) * Custom `setdiff()` translation (#3493) * Custom `db_explain()` translation (#3471) * SQLite * Correct translation for `as.numeric()`/`as.double()` (@chris-park, #171). * Redshift * `substr()` translation improved (#3339) ## Minor improvements and bug fixes * `copy_to()` will only remove existing table when `overwrite = TRUE` and the table already exists, eliminating a confusing "NOTICE" from PostgreSQL (#3197). * `partial_eval()` handles unevaluated formulas (#184). * `pull.tbl_sql()` now extracts correctly from grouped tables (#3562). * `sql_render.op()` now correctly forwards the `con` argument (@kevinykuo, #73). # dbplyr 1.2.2 * R CMD check fixes # dbplyr 1.2.1 * Forward compatibility fixes for rlang 0.2.0 # dbplyr 1.2.0 ## New top-level translations * New translations for * MS Access (#2946) (@DavisVaughan) * Oracle, via odbc or ROracle (#2928, #2732, @edgararuiz) * Teradata. * Redshift. * dbplyr now supplies appropriate translations for the RMariaDB and RPostgres packages (#3154). We generally recommend using these packages in favour of the older RMySQL and RPostgreSQL packages as they are fully DBI compliant and tested with DBItest. ## New features * `copy_to()` can now "copy" tbl_sql in the same src, providing another way to cache a query into a temporary table (#3064). You can also `copy_to` tbl_sqls from another source, and `copy_to()` will automatically collect then copy. * Initial support for stringr functions: `str_length()`, `str_to_upper()`, `str_to_lower()`, `str_replace_all()`, `str_detect()`, `str_trim()`. Regular expression support varies from database to database, but most simple regular expressions should be ok. ## Tools for developers * `db_compute()` gains an `analyze` argument to match `db_copy_to()`. * New `remote_name()`, `remote_con()`, `remote_src()`, `remote_query()` and `remote_query_plan()` provide a standard API for get metadata about a remote tbl (#3130, #2923, #2824). * New `sql_expr()` is a more convenient building block for low-level SQL translation (#3169). * New `sql_aggregate()` and `win_aggregate()` for generating SQL and windowed SQL functions for aggregates. These take one argument, `x`, and warn if `na.rm` is not `TRUE` (#3155). `win_recycled()` is equivalent to `win_aggregate()` and has been soft-deprecated. * `db_write_table` now needs to return the table name ## Minor improvements and bug fixes * Multiple `head()` calls in a row now collapse to a single call. This avoids a printing problem with MS SQL (#3084). * `escape()` now works with integer64 values from the bit64 package (#3230) * `if`, `ifelse()`, and `if_else()` now correctly scope the false condition so that it only applies to non-NULL conditions (#3157) * `ident()` and `ident_q()` handle 0-length inputs better, and should be easier to use with S3 (#3212) * `in_schema()` should now work in more places, particularly in `copy_to()` (#3013, @baileych) * SQL generation for joins no longer gets stuck in a endless loop if you request an empty suffix (#3220). * `mutate()` has better logic for splitting a single mutate into multiple subqueries (#3095). * Improved `paste()` and `paste0()` support in MySQL, PostgreSQL (#3168), and RSQLite (#3176). MySQL and PostgreSQL gain support for `str_flatten()` which behaves like `paste(x, collapse = "-")` (but for technical reasons can't be implemented as a straightforward translation of `paste()`). * `same_src.tbl_sql()` now performs correct comparison instead of always returning `TRUE`. This means that `copy = TRUE` once again allows you to perform cross-database joins (#3002). * `select()` queries no longer alias column names unnecessarily (#2968, @DavisVaughan). * `select()` and `rename()` are now powered by tidyselect, fixing a few renaming bugs (#3132, #2943, #2860). * `summarise()` once again performs partial evaluation before database submission (#3148). * `test_src()` makes it easier to access a single test source. ## Database specific improvements * MS SQL * Better support for temporary tables (@Hong-Revo) * Different translations for filter/mutate contexts for: `NULL` evaluation (`is.na()`, `is.null()`), logical operators (`!`, `&`, `&&`, `|`, `||`), and comparison operators (`==`, `!=`, `<`, `>`, `>=`, `<=`) * MySQL: `copy_to()` (via `db_write_table()`) correctly translates logical variables to integers (#3151). * odbc: improved `n()` translation in windowed context. * SQLite: improved `na_if` translation (@cwarden) * PostgreSQL: translation for `grepl()` added (@zozlak) * Oracle: changed VARVHAR to VARCHAR2 datatype (@washcycle, #66) # dbplyr 1.1.0 ## New features * `full_join()` over non-overlapping columns `by = character()` translated to `CROSS JOIN` (#2924). * `case_when()` now translates to SQL "CASE WHEN" (#2894) * `x %in% c(1)` now generates the same SQL as `x %in% 1` (#2898). * New `window_order()` and `window_frame()` give you finer control over the window functions that dplyr creates (#2874, #2593). * Added SQL translations for Oracle (@edgararuiz). ## Minor improvements and bug fixes * `x %in% c(1)` now generates the same SQL as `x %in% 1` (#2898). * `head(tbl, 0)` is now supported (#2863). * `select()`ing zero columns gives a more information error message (#2863). * Variables created in a join are now disambiguated against other variables in the same table, not just variables in the other table (#2823). * PostgreSQL gains a better translation for `round()` (#60). * Added custom `db_analyze_table()` for MS SQL, Oracle, Hive and Impala (@edgararuiz) * Added support for `sd()` for aggregate and window functions (#2887) (@edgararuiz) * You can now use the magrittr pipe within expressions, e.g. `mutate(mtcars, cyl %>% as.character())`. * If a translation was supplied for a summarise function, but not for the equivalent windowed variant, the expression would be translated to `NULL` with a warning. Now `sql_variant()` checks that all aggregate functions have matching window functions so that correct translations or clean errors will be generated (#2887) # dbplyr 1.0.0 ## New features * `tbl()` and `copy_to()` now work directly with DBI connections (#2423, #2576), so there is no longer a need to generate a dplyr src. ```R library(dplyr) con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") copy_to(con, mtcars) mtcars2 <- tbl(con, "mtcars") mtcars2 ``` * `glimpse()` now works with remote tables (#2665) * dplyr has gained a basic SQL optimiser, which collapses certain nested SELECT queries into a single query (#1979). This will improve query execution performance for databases with less sophisticated query optimisers, and fixes certain problems with ordering and limits in subqueries (#1979). A big thanks goes to @hhoeflin for figuring out this optimisation. * `compute()` and `collapse()` now preserve the "ordering" of rows. This only affects the computation of window functions, as the rest of SQL does not care about row order (#2281). * `copy_to()` gains an `overwrite` argument which allows you to overwrite an existing table. Use with care! (#2296) * New `in_schema()` function makes it easy to refer to tables in schema: `in_schema("my_schema_name", "my_table_name")`. ## Deprecated and defunct * `query()` is no longer exported. It hasn't been useful for a while so this shouldn't break any code. ## Verb-level SQL generation * Partial evaluation occurs immediately when you execute a verb (like `filter()` or `mutate()`) rather than happening when the query is executed (#2370). * `mutate.tbl_sql()` will now generate as many subqueries as necessary so that you can refer to variables that you just created (like in mutate with regular dataframes) (#2481, #2483). * SQL joins have been improved: * SQL joins always use the `ON ...` syntax, avoiding `USING ...` even for natural joins. Improved handling of tables with columns of the same name (#1997, @javierluraschi). They now generate SQL more similar to what you'd write by hand, eliminating a layer or two of subqueries (#2333) * [API] They now follow the same rules for including duplicated key variables that the data frame methods do, namely that key variables are only kept from `x`, and never from `y` (#2410) * [API] The `sql_join()` generic now gains a `vars` argument which lists the variables taken from the left and right sides of the join. If you have a custom `sql_join()` method, you'll need to update how your code generates joins, following the template in `sql_join.generic()`. * `full_join()` throws a clear error when you attempt to use it with a MySQL backend (#2045) * `right_join()` and `full_join()` now return results consistent with local data frame sources when there are records in the right table with no match in the left table. `right_join()` returns values of `by` columns from the right table. `full_join()` returns coalesced values of `by` columns from the left and right tables (#2578, @ianmcook) * `group_by()` can now perform an inline mutate for database backends (#2422). * The SQL generation set operations (`intersect()`, `setdiff()`, `union()`, and `union_all()`) have been considerably improved. By default, the component SELECT are surrounded with parentheses, except on SQLite. The SQLite backend will now throw an error if you attempt a set operation on a query that contains a LIMIT, as that is not supported in SQLite (#2270). All set operations match column names across inputs, filling in non-matching variables with NULL (#2556). * `rename()` and `group_by()` now combine correctly (#1962) * `tbl_lazy()` and `lazy_tbl()` have been exported. These help you test generated SQL with out an active database connection. * `ungroup()` correctly resets grouping variables (#2704). ## Vector-level SQL generation * New `as.sql()` safely coerces an input to SQL. * More translators for `as.character()`, `as.integer()` and `as.double()` (#2775). * New `ident_q()` makes it possible to specifier identifiers that do not need to be quoted. * Translation of inline scalars: * Logical values are now translated differently depending on the backend. The default is to use "true" and "false" which is the SQL-99 standard, but not widely support. SQLite translates to "0" and "1" (#2052). * `Inf` and `-Inf` are correctly escaped * Better test for whether or not a double is similar to an integer and hence needs a trailing 0.0 added (#2004). * Quoting defaults to `DBI::dbEscapeString()` and `DBI::dbQuoteIdentifier()` respectively. * `::` and `:::` are handled correctly (#2321) * `x %in% 1` is now correctly translated to `x IN (1)` (#511). * `ifelse()` and `if_else()` use correct argument names in SQL translation (#2225). * `ident()` now returns an object with class `c("ident", "character")`. It no longer contains "sql" to indicate that this is not already escaped. * `is.na()` and `is.null()` gain extra parens in SQL translation to preserve correct precedence (#2302). * [API] `log(x, b)` is now correctly translated to the SQL `log(b, x)` (#2288). SQLite does not support the 2-argument log function so it is translated to `log(x) / log(b)`. * `nth(x, i)` is now correctly translated to `nth_value(x, i)`. * `n_distinct()` now accepts multiple variables (#2148). * [API] `substr()` is now translated to SQL, correcting for the difference in the third argument. In R, it's the position of the last character, in SQL it's the length of the string (#2536). * `win_over()` escapes expression using current database rules. ## Backends * `copy_to()` now uses `db_write_table()` instead of `db_create_table()` and `db_insert_into()`. `db_write_table.DBIConnection()` uses `dbWriteTable()`. * New `db_copy_to()`, `db_compute()` and `db_collect()` allow backends to override the entire database process behind `copy_to()`, `compute()` and `collect()`. `db_sql_render()` allow additional control over the SQL rendering process. * All generics whose behaviour can vary from database to database now provide a DBIConnection method. That means that you can easily scan the NAMESPACE to see the extension points. * `sql_escape_logical()` allows you to control the translation of literal logicals (#2614). * `src_desc()` has been replaced by `db_desc()` and now dispatches on the connection, eliminating the last method that required dispatch on the class of the src. * `win_over()`, `win_rank()`, `win_recycled()`, `win_cumulative()`, `win_current_group()` and `win_current_order()` are now exported. This should make it easier to provide customised SQL for window functions (#2051, #2126). * SQL translation for Microsoft SQL Server (@edgararuiz) * SQL translation for Apache Hive (@edgararuiz) * SQL translation for Apache Impala (@edgararuiz) ## Minor bug fixes and improvements * `collect()` once again defaults to return all rows in the data (#1968). This makes it behave the same as `as.data.frame()` and `as_tibble()`. * `collect()` only regroups by variables present in the data (#2156) * `collect()` will automatically LIMIT the result to the `n`, the number of rows requested. This will provide the query planner with more information that it may be able to use to improve execution time (#2083). * `common_by()` gets a better error message for unexpected inputs (#2091) * `copy_to()` no longer checks that the table doesn't exist before creation, instead preferring to fall back on the database for error messages. This should reduce both false positives and false negative (#1470) * `copy_to()` now succeeds for MySQL if a character column contains `NA` (#1975, #2256, #2263, #2381, @demorenoc, @eduardgrebe). * `copy_to()` now returns it's output invisibly (since you're often just calling for the side-effect). * `distinct()` reports improved variable information for SQL backends. This means that it is more likely to work in the middle of a pipeline (#2359). * Ungrouped `do()` on database backends now collects all data locally first (#2392). * Call `dbFetch()` instead of the deprecated `fetch()` (#2134). Use `DBI::dbExecute()` for non-query SQL commands (#1912) * `explain()` and `show_query()` now invisibly return the first argument, making them easier to use inside a pipeline. * `print.tbl_sql()` displays ordering (#2287) and prints table name, if known. * `print(df, n = Inf)` and `head(df, n = Inf)` now work with remote tables (#2580). * `db_desc()` and `sql_translate_env()` get defaults for DBIConnection. * Formatting now works by overriding the `tbl_sum()` generic instead of `print()`. This means that the output is more consistent with tibble, and that `format()` is now supported also for SQL sources (tidyverse/dbplyr#14). ## Lazy ops * [API] The signature of `op_base` has changed to `op_base(x, vars, class)` * [API] `translate_sql()` and `partial_eval()` have been refined: * `translate_sql()` no longer takes a vars argument; instead call `partial_eval()` yourself. * Because it no longer needs the environment `translate_sql()_` now works with a list of dots, rather than a `lazy_dots`. * `partial_eval()` now takes a character vector of variable names rather than a tbl. * This leads to a simplification of the `op` data structure: dots is now a list of expressions rather than a `lazy_dots`. * [API] `op_vars()` now returns a list of quoted expressions. This enables escaping to happen at the correct time (i.e. when the connection is known). dbplyr/MD50000644000176200001440000004301114033054373012057 0ustar liggesusers363b2b2617554848caac19cb99114816 *DESCRIPTION 3b44dfe91f4912ca3b742aee84e32745 *LICENSE bfd3a94392f24f461247be97e5ca6c34 *NAMESPACE dd9e518233d43f809e0711dd34f1aae2 *NEWS.md 6f0ff852521aa09a94584335aec60c89 *R/backend-.R c54f09185c63f59dc48d75666ec09174 *R/backend-access.R 522147a7956658a271fec2fc944a0bcb *R/backend-hana.R 87cb19b6d73b1ff831b449a111317839 *R/backend-hive.R 5388a5069ebe6afb594d9312a641d6ad *R/backend-impala.R 4aae9ededfdf0ea37509ab9e3ee4111e *R/backend-mssql.R 494cc0ab8bb671982658bdf3486792ee *R/backend-mysql.R c30f3e2b3addd3e694b6326e3e422bb5 *R/backend-odbc.R 2ebc8240e9c092a7ee94052fe2343d85 *R/backend-oracle.R 7f4a39367987ae76db503aa6d246880d *R/backend-postgres-old.R 3f909faeb1b4c20505dcfa1bc9785781 *R/backend-postgres.R c9a8c4245ba67aca16c1fc3fdbe55547 *R/backend-redshift.R 3b3068c0d0c7e080dbc7510a465773bf *R/backend-snowflake.R 297898396ee282688fda509eb2f142ab *R/backend-sqlite.R 89ce1b39b35d58b8a414c6b2dbd1b6e1 *R/backend-teradata.R bd0dcd33db4b78d8c1e57433fabec232 *R/build-sql.R 0a2df2d05ed6d1c74ad93655ecab9234 *R/data-cache.R 88a6ce9160604c63852c553ccffa5f47 *R/data-lahman.R 4c8540ed10f4e2d2046ab1466c46724d *R/data-nycflights13.R bc646a97964ea0879f3c586c9f3e6442 *R/db-escape.R 4685ad012400cad604162b6ca1315696 *R/db-io.R 9aedda365f207bd0fc4161069c92b1a5 *R/db-sql.R 01095bd823c2b29d26efeef135ce1e0a *R/db.R 8b1b69e7c10e39de76b8dcabd023fe85 *R/dbplyr.R a804d1b0e5b626d6746716839b5f4af6 *R/escape.R 219606dc2a37b0cc41f92e106eb07d49 *R/explain.R 9d1dca28857597adcc04346bcb4de0bc *R/ident.R 3e1869f08ba30aa7488c6a970fe1a185 *R/lazy-ops.R d7ba5edb38628e5f915b4e875bb22688 *R/memdb.R 49cccaec29f018221e8077db651c4f45 *R/partial-eval.R c68635b0937d75bb3dd77f0ca7524610 *R/progress.R 594a16b05a79981018fe809ef4466d7a *R/query-join.R 186980f068cd92662c77c32aa77fd040 *R/query-select.R 69183c912833c0d91faaf5c456e3f922 *R/query-semi-join.R d972af7a7883aa2267fd0ca848faa50c *R/query-set-op.R 5728dd0092cb05bbb985d63a8f92d220 *R/query.R 247d405f289f070db42a19c4c41a15b2 *R/reexport.R c5326d097098b00585026ac8bb1fb877 *R/remote.R b34477c39400031c67a3defda9859f2d *R/schema.R 3fae733d7cc80905d7c3a7f082768374 *R/simulate.R 28156b40be3846fd330ea9e276504040 *R/sql-build.R da3fb120aee02dd09f9883d966f83392 *R/sql-clause.R b2fbb6cd86bbd3b2c198e0d9658db1df *R/sql-expr.R b1dfa93226756c24876986ece6ad0c8d *R/sql.R 78ea5d29511334e31924e289384b74ab *R/src-sql.R c5ee100bcfab4ede7327d5e4d1bf7e41 *R/src_dbi.R d42ad58ae99c898dde4cedc85e7889e9 *R/tbl-lazy.R a38cce1a6883a9cbd5323ed08acec66a *R/tbl-sql.R 67baa8b889f5a97f1a0927b71686608c *R/test-frame.R af7a909243a02c5914f1cde70c991772 *R/testthat.R 90caba3723026544dfa04a59a674945b *R/translate-sql-conditional.R f164044a95ba79fa9f1d77b7abe1b9f4 *R/translate-sql-helpers.R be129d2f1a63b83dfd7e386ef10b71c3 *R/translate-sql-paste.R b065f4ecad66df0aa0e2162dda04e7f9 *R/translate-sql-quantile.R adb7715708cb5685208ad860f8def1e1 *R/translate-sql-string.R 5435a09196fbec943b4bd39ec84a5897 *R/translate-sql-window.R 47c4c91e8df159ea3b2e773192dc005b *R/translate-sql.R f40e978f9e7564b39cf781689b1e6a09 *R/utils-format.R 04e90118aa5c07fb2ff6727a2ef06424 *R/utils.R df59bfcf5676587b812628c582b22c78 *R/verb-arrange.R dff79070a8e0fe9052f84e671674d646 *R/verb-compute.R 21b17e59ae52d74b1fcca992198db3db *R/verb-copy-to.R abaccd06ac906d4183ff12c214ea05b1 *R/verb-count.R 2f7769074db1110e95f3f51e30690c1a *R/verb-distinct.R bb67a6681e44bdcf91bfb746b0e765fd *R/verb-do-query.R 28802d1bd122d5bbb75c0a2d9f9db583 *R/verb-do.R 1b911ed598ffb49a9fc58df70811a02e *R/verb-expand.R 6894ff3932367da2373db0da22ed148a *R/verb-fill.R 1d9dcfe636edc9f8b30a4c725675b9b2 *R/verb-filter.R b4ecabc20e273e8df88ad9c75ea4702b *R/verb-group_by.R 2d2d2995c6dffa5378cd611871977249 *R/verb-head.R 3129b05b5c14d8131bf12235db7ee370 *R/verb-joins.R ec5b26906b2221e05429fe05e5d3fc4c *R/verb-mutate.R a8a5b82ef69cb28e3071717cb6cf256e *R/verb-pivot-longer.R b9b2753adf8f2bdbef6a212588f90900 *R/verb-pivot-wider.R f9c46fa5e97e7a87fc9b469918221fe5 *R/verb-pull.R 15e2dc7505b1cb3944ea66d0ad9045cf *R/verb-select.R 5e1bfa761139371a3d0bb6b2bff12d42 *R/verb-set-ops.R 647b8bfa32226b2d75db4b046da94490 *R/verb-slice.R b200035117574aa43831510deecd5470 *R/verb-summarise.R e17a747983a20bc7ac4cd5614b9a2e08 *R/verb-uncount.R d2742701be115d3910c973bd0a54a591 *R/verb-window.R d1f43aa7ec84fd6242fbf7b9f3565fdf *R/zzz.R f2d807cb6bc1e9a27e07fe45e2fb957c *README.md 9be697c48cd21699481b7b092a651b90 *build/dbplyr.pdf dd422b059afde5897adf63f9ed243a43 *build/vignette.rds dee34e1ca8a6ac263113a99d6180be26 *inst/doc/backend-2.R 042e0e13431d0bd873b81dea489f0481 *inst/doc/backend-2.Rmd 96a64f8525251ddaa1f210cb84385373 *inst/doc/backend-2.html e017ab655818a301dd463bf446af768c *inst/doc/dbplyr.R c6c4fe7f08196d7dedc20ffe7d9bb335 *inst/doc/dbplyr.Rmd 8f654fb45e92d56d113b40b63e4785a6 *inst/doc/dbplyr.html 4d5d2e3f328f7b44658c2bb6d0085ac3 *inst/doc/new-backend.R 8bee29fcfe859ff9de141c7a807d4dd2 *inst/doc/new-backend.Rmd 286adfee7e35ac488ba3c21b1678b4a0 *inst/doc/new-backend.html ebbf6478f6b5b44b46d2d4a885e86f5c *inst/doc/reprex.R cd350d3f501967e8c133480e0182c6b2 *inst/doc/reprex.Rmd 8c7531b7840c15869254f093d95181b0 *inst/doc/reprex.html dc66c75e3aaae8d49ce21563951b0c3b *inst/doc/sql.R c9bd277aab2bc659e4560e3e0bf48a2e *inst/doc/sql.Rmd 6f377c24a21292060f30d14f0d96c4b4 *inst/doc/sql.html 38dd05c3f5bad528e0612289d81947f6 *inst/doc/translation-function.R 62545aa4344134aaa8dbe2cb7188645f *inst/doc/translation-function.Rmd cdd9a8866e7b7b621b15eb92626b7316 *inst/doc/translation-function.html 81d404feaa5218c86dfe1c665093a91b *inst/doc/translation-verb.R 4a88396255ba15b5ea70826e1602cdf0 *inst/doc/translation-verb.Rmd 05e5aee043f6ca75ff8e93f10ad00c8e *inst/doc/translation-verb.html d5d8817de47ff1e1233ef20e2bcb5702 *man/arrange.tbl_lazy.Rd 33f6d9553dce60cceae32d6db682abc7 *man/backend-access.Rd 104262a0f0c926944759fdd1b099165a *man/backend-hana.Rd bf5b4450029b04fcd3e377e9ff424785 *man/backend-hive.Rd 473239ce32fe04147af29907b0d1c408 *man/backend-impala.Rd 5562f40fdd6ff4743b55d3e912f661ff *man/backend-mssql.Rd 8ae9125ed4e4df20ef24e265b3b676c6 *man/backend-mysql.Rd 2aab8da364ea9971416316714fe00ae4 *man/backend-odbc.Rd 25abeca3a926d71924ab3ec2e2013a0f *man/backend-oracle.Rd 81ab69e2c7cd7cb303598ad441869fa5 *man/backend-postgres.Rd d276fad1d304e35e20accdbb39b39948 *man/backend-redshift.Rd 57304515c6c78b85a9905e0ba6f6c5be *man/backend-snowflake.Rd b2d7f9f066294acd00b283492b76f62d *man/backend-sqlite.Rd 9d1ca4a0a9559b2bd977c3534611ad75 *man/backend-teradata.Rd 01dd69830eb85e0436331673d9453ab1 *man/build_sql.Rd 992735dd78307f69879f14c13ccf6c05 *man/collapse.tbl_sql.Rd fad9433ac48b264133c8785553c118da *man/complete.tbl_lazy.Rd 57a5babc8f633db97577bf22a031da36 *man/copy_to.src_sql.Rd 766b2f0e8e2bdb662272e2786e136932 *man/count.tbl_lazy.Rd 442e7b3940fcc473abe4d5c2dfbab59a *man/db-io.Rd c30b8f7445ed5329ee228e63b33c1072 *man/db-misc.Rd df7efc5dbb7394fcfa87ae8ef4767354 *man/db-quote.Rd 2384336a90182d65e2a4b6c459fd6896 *man/db-sql.Rd 2f8ff32456330ec12792b7007920276b *man/dbplyr-package.Rd e4b31aed890d022d5ae2c59346feb53f *man/dbplyr-slice.Rd 7d93d64c9e049f8bc884a395d08807ac *man/dbplyr_uncount.Rd e06a9816f40dec8172f5db0315bf4c71 *man/distinct.tbl_lazy.Rd 7d145db470cd2f3dbdfac3eda32eb354 *man/do.tbl_sql.Rd 631b43094d3d7c5825f8249e66869b68 *man/escape.Rd 6ea954b0a37a4237719fb4217ec16af2 *man/expand.tbl_lazy.Rd cb1e46f469cfbbbde29c8b5113e1d789 *man/figures/lifecycle-archived.svg c0d2e5a54f1fa4ff02bf9533079dd1f7 *man/figures/lifecycle-defunct.svg a1b8c987c676c16af790f563f96cbb1f *man/figures/lifecycle-deprecated.svg c3978703d8f40f2679795335715e98f4 *man/figures/lifecycle-experimental.svg 952b59dc07b171b97d5d982924244f61 *man/figures/lifecycle-maturing.svg 27b879bf3677ea76e3991d56ab324081 *man/figures/lifecycle-questioning.svg 6902bbfaf963fbc4ed98b86bda80caa2 *man/figures/lifecycle-soft-deprecated.svg 53b3f893324260b737b3c46ed2a0e643 *man/figures/lifecycle-stable.svg 1c1fe7a759b86dc6dbcbe7797ab8246c *man/figures/lifecycle-superseded.svg 5e2eb571cdc06c516b59fb8008afa7d6 *man/figures/logo.png a645374c31aca1cceba04cdbf26a89ea *man/fill.tbl_lazy.Rd 4ea9e618fe47d3e06728f7abed3a684d *man/filter.tbl_lazy.Rd 9622fc59324e22bf387c7def3732fb0a *man/group_by.tbl_lazy.Rd 63d5a212b7af4686637ff109f2256ec0 *man/head.tbl_lazy.Rd 63a892afd16fa5f35e97358459940609 *man/ident.Rd 8800e053b75e2ea22232f9fdb36c7b03 *man/ident_q.Rd 94ebface0f066eb90333bc81fb28fffa *man/in_schema.Rd 4fc3b0cacc54e547e54f0838ae1c057d *man/intersect.tbl_lazy.Rd e16b928ba2e8c52722550b7a35485d7c *man/join.tbl_sql.Rd 9cbbb2f873d0af94253b60a2a7081963 *man/lahman.Rd 22606e77ad1a306a7cf0da4e5679db2c *man/lazy_ops.Rd 32aaa3bed76e6190bbe8b574d3abd3a9 *man/memdb_frame.Rd 0ac0fe8b3702762eb209b9a880b40524 *man/mutate.tbl_lazy.Rd 9e733c531fd12648436d76b356c3fbdf *man/named_commas.Rd 629abac3cd3cd83f6571df761612cc1e *man/nycflights13.Rd 94915409e8b9014406bfd4988399a150 *man/partial_eval.Rd 7ab79607b85ceda5b075070293af66bb *man/pivot_longer.tbl_lazy.Rd 351843f30bf566f5702097e5031f2294 *man/pivot_wider.tbl_lazy.Rd ec2a4814b561b6d4e04fbffe28af4bf5 *man/pull.tbl_sql.Rd e4284720c81d9fe361e9e2e0a1a5942e *man/reexports.Rd 3cd83bd0677f9b1d02f7600ec286a76f *man/remote_name.Rd 34602bf2535b004c5600a9367c352dff *man/replace_na.tbl_lazy.Rd d2affc875e2994147adb2e123dfa82d4 *man/select.tbl_lazy.Rd 07eaa8de7970933a87fcb47ac17c08aa *man/simulate_dbi.Rd f157e4d6e688d2a75c412f003c5fc58d *man/sql.Rd dab201a2353517a6d483797e8a9c065c *man/sql_build.Rd bb4ffb9bf30684409b704c9bb91e7b8b *man/sql_expr.Rd 5bf7cb049cf82b7f3bfdfba3afbf5553 *man/sql_quote.Rd f0bdff88c86db6099dea6a56e8f11c2c *man/sql_variant.Rd b62b3f1c3c2a476e73acadd42db60816 *man/src_dbi.Rd 901327fa6fbc38a6083a6c68f50111c5 *man/src_sql.Rd d15d4f9f495b407a43cb95145304ef27 *man/summarise.tbl_lazy.Rd 7fb6c7b3d8ca420fd32b21b114427574 *man/tbl.src_dbi.Rd 7dc2965b705adaaea4362b63611a9ff5 *man/tbl_lazy.Rd 091c41e2c1905c25815b9466dc630a26 *man/tbl_sql.Rd 3573e74d70f331a8932570cfbc45875d *man/testing.Rd b438e058cf70e6153544c270040d057a *man/translate_sql.Rd fd56e9d669ce0bb6958e4b82d9f9acd9 *man/win_over.Rd 2d7bc9c02e64de1abb6f0fb4a99ba8f0 *man/window_order.Rd c1a44682d2c53dba690e0222461853ae *tests/testthat.R f7e65b94f5800a45e882f6bc74bb4ae2 *tests/testthat/_snaps/backend-.md 61f20de2d8c52fc9628a1de83a281f7e *tests/testthat/_snaps/backend-access.md c59407ff3c20e04978e4a9e223a10470 *tests/testthat/_snaps/backend-hana.md 9090cb2e5f36787546d42678aeb75b29 *tests/testthat/_snaps/backend-hive.md e07caa736b12db0f9063299798589731 *tests/testthat/_snaps/backend-impala.md c2ecef6ebd71e657ea9a84ca8207521b *tests/testthat/_snaps/backend-mssql.md 6541a09c54d8e7a81eb8344e852fbf17 *tests/testthat/_snaps/backend-mysql.md e29744fdbdcd04c74a1aa8918406e9d3 *tests/testthat/_snaps/backend-oracle.md 78d0992ea4968a5af61bf137241103e6 *tests/testthat/_snaps/backend-postgres.md d6e77d906404936de3757e22b2ddc9ba *tests/testthat/_snaps/backend-sqlite.md 81d08e680acaad9ead439c0c75381526 *tests/testthat/_snaps/backend-teradata.md eb093f8f65917a62c71b502689db0d9e *tests/testthat/_snaps/escape.md 48062468b1a55f16afc51be2b3b94fbf *tests/testthat/_snaps/partial-eval.md 20a21656120a1cb769afb9e83a230a0f *tests/testthat/_snaps/query-join.md 75a269d1cf2f256e0d213e9c0679d0c1 *tests/testthat/_snaps/query-select.md 0779d08728ee7ac69d95a61aa69274f1 *tests/testthat/_snaps/query-semi-join.md a637ad30fc3beede7c3620b584aaf52c *tests/testthat/_snaps/query-set-op.md 27c8f004d29dc49a1c256ca4252aebda *tests/testthat/_snaps/schema.md f7cc49d0031d0074acfb328c22cbe44a *tests/testthat/_snaps/sql-build.md dbe57ad5458a691bb6967fd0b544c55e *tests/testthat/_snaps/tbl-lazy.md 11fac3b5f920ffbcc09e81a66e6dbc32 *tests/testthat/_snaps/translate-sql-conditional.md 288a7121f7da98c1f1b1df13a66de351 *tests/testthat/_snaps/translate-sql-helpers.md b6107c6f91a1375251f6e21170745711 *tests/testthat/_snaps/translate-sql-quantile.md 0479e67367c2953862f8da5f8c6a1768 *tests/testthat/_snaps/translate-sql-string.md 2bced713b231ca77a5d95dac9c3bc73d *tests/testthat/_snaps/translate-sql.md 14b31a119a8aed87dd37c3987374389d *tests/testthat/_snaps/verb-arrange.md f21691a1a6f456c203c6c68463d4605d *tests/testthat/_snaps/verb-count.md c660e54bb88396e88c6d8476daa9f31f *tests/testthat/_snaps/verb-distinct.md d6193a8ed59b01750d76131870d7e41d *tests/testthat/_snaps/verb-expand.md 9dd1f5a93bec4b73d3f773655f5860c4 *tests/testthat/_snaps/verb-fill.md 3bf6866951d696653b9a1d5e629fbcd7 *tests/testthat/_snaps/verb-joins.md cd0b9653499d13f161ac30d62161f62d *tests/testthat/_snaps/verb-mutate.md 68a623637fda1064a911b2c2c4fe0e2a *tests/testthat/_snaps/verb-pivot-longer.md 4e2da29d013933329c7b8d3d09b6a307 *tests/testthat/_snaps/verb-pivot-wider.md 4b0764aa843745b3f04d8ff709eff3fc *tests/testthat/_snaps/verb-select.md 642c0cea8ed7e7bfdc81a3388030632d *tests/testthat/_snaps/verb-slice.md 3f121f38a62732bcf49ee1b6abcd4d8f *tests/testthat/_snaps/verb-summarise.md a5746016b47e6f8e5fe6e38a23937137 *tests/testthat/_snaps/verb-uncount.md b1bc3f050fad82ab9ce8d27ea1ac87d3 *tests/testthat/helper-src.R 5a1d52da79182df136d844b87cc80abe *tests/testthat/test-backend-.R fb7fda245f506df0afbbf95aa92a5081 *tests/testthat/test-backend-access.R cc644cf0208282e8c83289eff5bb6b9f *tests/testthat/test-backend-hana.R a629fb2a87fa04d170133ba626ea65c3 *tests/testthat/test-backend-hive.R 4e6b0a5b09718574a1c46731379ba2db *tests/testthat/test-backend-impala.R 49d708290bb6b26a764a5df23e2a5256 *tests/testthat/test-backend-mssql.R 72cdf929803e3e743ca03b818b8ed4ad *tests/testthat/test-backend-mysql.R 4ddea2bcf392ddd50cfd353ed9a75799 *tests/testthat/test-backend-odbc.R d1494fbaef51b2e33bccdd1a1122ce3f *tests/testthat/test-backend-oracle.R f41a4b49451bf6912b55329d11428f45 *tests/testthat/test-backend-postgres-old.R 29407150c7702c95f7077ba568313d91 *tests/testthat/test-backend-postgres.R 5b4bd33e3365c2c79eb21eeada40bb2b *tests/testthat/test-backend-redshift.R f7efdc632acaf0ab7aa4906625ef2b68 *tests/testthat/test-backend-sqlite.R 4241c6a1ea7861cbfce6916abe28d89f *tests/testthat/test-backend-teradata.R 0623b80910fe635687220566a21809bd *tests/testthat/test-db-sql.R 08f08e3248a2bdf5ea2074b91bf513ab *tests/testthat/test-escape.R ff4dad293189dacced9ff464526dc8ef *tests/testthat/test-ident.R 1f4875260a1bd4f433bb4e70f36ecc20 *tests/testthat/test-partial-eval.R 3966e20f9af06c3686cbf47fa1dd4e95 *tests/testthat/test-query-join.R b14f770d8806a666ef4431332d5b16ce *tests/testthat/test-query-select.R aefea44ad6e30400c8b7fc58d8ffe84f *tests/testthat/test-query-semi-join.R 70be43468308364ca168e867d2eb81d5 *tests/testthat/test-query-set-op.R 00c4c7f004cdde26482af1e2b41ede19 *tests/testthat/test-remote.R 6825732b470677b6828c2d6263eeb932 *tests/testthat/test-schema.R 68671fbc27e20909bd2d8879b5c10389 *tests/testthat/test-sql-build.R 7bd75b6d9abf4396728b92b88cb353e6 *tests/testthat/test-sql-expr.R 5ed111c99780d1eeb242765a2a58f2a8 *tests/testthat/test-sql.R 5213b152c2aad622723b4fe9b03eb071 *tests/testthat/test-src_dbi.R 888b52751001f2cda302de29d94a3b74 *tests/testthat/test-tbl-lazy.R c1816bbc3ab5edf4078f15d343d18531 *tests/testthat/test-tbl-sql.R ef11d28dde6d5a93d98619068187ed23 *tests/testthat/test-test-backend-snowflake.R fece878829080e6331b62f78af761b5c *tests/testthat/test-translate-sql-conditional.R 9eec82004501ad2d94f64448a02c47de *tests/testthat/test-translate-sql-helpers.R 58a5a213b53459e6044fbd2334944222 *tests/testthat/test-translate-sql-paste.R 389a00f1d46027cc2de50c9ebb005cd5 *tests/testthat/test-translate-sql-quantile.R 232c7fe2216a05e6de598e832f2ac130 *tests/testthat/test-translate-sql-string.R e1054a3beecb8b72414ff16763d2dfff *tests/testthat/test-translate-sql-window.R 449c471873b27479c0ca72337c758335 *tests/testthat/test-translate-sql.R f942a71a2d69f72e370726c9bba3c1e6 *tests/testthat/test-utils.R 26603d0d4068321333a0746e6cdb3522 *tests/testthat/test-verb-arrange.R a9f9db56e1ae2ab36c2abd5aab1342aa *tests/testthat/test-verb-compute.R 26f140bb4ced19960d8bfa0d7a0125cd *tests/testthat/test-verb-copy-to.R a86cb22d4c76985c7f8459bc2629354f *tests/testthat/test-verb-count.R 786df2b078f3f9bce31cfa89748dd6cb *tests/testthat/test-verb-distinct.R 576fa76ab1ae5e82729948bb2bff8252 *tests/testthat/test-verb-do.R 0ac5ce3ebdfc78f64a27d9249d3783e1 *tests/testthat/test-verb-expand.R b2e350cd4679ccd886e1d50c6043c132 *tests/testthat/test-verb-fill.R 94248da2857bb1ba407c8c3477656f78 *tests/testthat/test-verb-filter.R a3c5b802af5d34f216ddebbdf857d51f *tests/testthat/test-verb-group_by.R 23ce029d66b2fde4b3fe76c6b9a83594 *tests/testthat/test-verb-head.R 0fbe63236abfb255f73c48b19dc53205 *tests/testthat/test-verb-joins.R 2d4c459dc58d629ed6ab9347d0bca1ab *tests/testthat/test-verb-mutate.R c72788591803dc613d41824d9cf9a48c *tests/testthat/test-verb-pivot-longer.R 430b7535db84bc3ff4997d17a7dd0d0d *tests/testthat/test-verb-pivot-wider.R 8fe9564a1bece62cb64bf869f83c605b *tests/testthat/test-verb-pull.R 2b05c08e428976e98c08e56359c1a6a4 *tests/testthat/test-verb-select.R 39ee86c088ef9a9cf3455519d7e81670 *tests/testthat/test-verb-set-ops.R 0860cc99b5dba67bac1d7b4e2a84d1b7 *tests/testthat/test-verb-slice.R f9eb68c13cba8db4c662d3a9b3c46b6d *tests/testthat/test-verb-summarise.R 7aa55a7bed775d03058758b767be19a7 *tests/testthat/test-verb-uncount.R 042e0e13431d0bd873b81dea489f0481 *vignettes/backend-2.Rmd c6c4fe7f08196d7dedc20ffe7d9bb335 *vignettes/dbplyr.Rmd 8bee29fcfe859ff9de141c7a807d4dd2 *vignettes/new-backend.Rmd cd350d3f501967e8c133480e0182c6b2 *vignettes/reprex.Rmd 821d987a4a2b64c8949a348685137999 *vignettes/setup/_mssql.Rmd c5873c753de4b18ae90898ec2da5eab5 *vignettes/setup/_mysql.Rmd 10fd12b79bc5885a22f4053b8b0a28bc *vignettes/setup/_postgres.Rmd e54749cdc109dd9a2c849205881523c5 *vignettes/setup/_sap-hana.Rmd c9bd277aab2bc659e4560e3e0bf48a2e *vignettes/sql.Rmd 62545aa4344134aaa8dbe2cb7188645f *vignettes/translation-function.Rmd 4a88396255ba15b5ea70826e1602cdf0 *vignettes/translation-verb.Rmd 83cdde894e0c44ffda5a9dbae3c80092 *vignettes/windows.graffle a21bea5bdf33d4e34a9dc472f7227de7 *vignettes/windows.png dbplyr/inst/0000755000176200001440000000000014033043056012521 5ustar liggesusersdbplyr/inst/doc/0000755000176200001440000000000014033043056013266 5ustar liggesusersdbplyr/inst/doc/backend-2.html0000644000176200001440000004052014033043050015675 0ustar liggesusers dplyr 2.0.0 backend API

dplyr 2.0.0 backend API

This transition guide is aimed at backend authors. dbplyr 2.0.0 is an important release for backends because it starts the process of moving all backend generics into dbplyr (instead of some living in dplyr). This move has been designed to occur in phases to avoid sudden breakages and give backend authors plenty of time to make changes.

The current timeline is something like this:

  • dbplyr 2.0.0 adds a new interface for database backends. The old interface remains so all existing backends continue to work, but new packages should use the new interface, and existing backends should start the update process. 

  • dbplyr 2.1.0 (to be released >= 6 months after dbplyr 2.0.0) deprecates the old interface, so that users are encouraged to upgrade backends.

  • dbplyr 2.2.0 (to be released >= 12 months after dbplyr 2.0.0) removes the old interface so user must upgrade backends.

  • A future version of dplyr will deprecate then remove the database generics.

Unused generics

A number of generics are no longer used so you can delete the corresponding methods:

  • db_write_table() calls DBI::dbWriteTable() instead of nine individual generics: db_create_indexes(), db_begin(), db_rollback(), db_commit(), db_list_tables(), db_drop_table(), db_has_table(), db_create_table(), and db_data_types().

  • sql_escape_ident() and sql_escape_string() are no longer used in favour of calling dbQuoteIdentifier() and dbQuoteString() directly.

  • db_query_rows() was never actually used.

Making these changes are important because they ensure your backend works consistently whether you use it through DBI or dplyr.

2nd edition

dbplyr 2.0.0 draws inspiration from the idea of an edition so that to tell dbplyr to use the new generics, you need to do two things:

  • Depend on dbplyr 2.0.0 in your DESCRIPTION, e.g. Imports: dbplyr (>= 2.0.0). This ensures that when someone installs your package they get the latest version of dbplyr.

  • Provide a method for the dbplyr_edition generic:

    #' @importFrom dbplyr dbplyr_edition
    #' @export
    dbplyr_edition.myConnectionClass <- function(con) 2L

    This tells dbplyr to use the new generics instead of the old generics.

Then you’ll need to update your methods, following the advice below.

SQL generation

There are a number of dplyr generics that generate then execute SQL. These have been replaced by dbplyr generics that just generate the SQL (and dbplyr takes care of executing it):

  • dplyr::db_analyze() -> dbplyr::sql_table_analyze()
  • dplyr::db_create_index() -> dbplyr::sql_table_index()
  • dplyr::db_explain() -> dbplyr::sql_query_explain()
  • dplyr::db_query_fields() -> dbplyr::sql_query_fields()
  • dplyr::db_save_query() -> dbplyr::sql_query_save()

If you have methods for any of those generics, you’ll need to extract the SQL generation code into a new sql_ method.

Renamed generics

A number of other generics have been renamed:

  • dplyr::sql_select() -> dbplyr::sql_query_select()
  • dplyr::sql_join() -> dbplyr::sql_query_join()
  • dplyr::sql_semi_join() -> dbplyr::sql_query_semi_join()
  • dplyr::sql_set_op() -> dbplyr::sql_query_set_op()
  • dplyr::sql_subquery() -> dbplyr::sql_query_wrap()
  • dplyr::sql_translate_env() -> dbplyr::sql_translation()
  • dplyr::db_desc() -> dbplyr::db_connection_describe()

If you have methods for any of these generics, you’ll need to rename.

New generics

You may also want to consider methods for the new generics in dbplyr 2.0.0:

  • Provide a method for db_temporary_table() if your backend requires that temporary tables have special names.

  • Provide a method for sql_expr_matches() if your database has special syntax for matching two values (see https://modern-sql.com/feature/is-distinct-from).

  • Provide a method for sql_join_suffix() if your backend can’t use the usual .x and .y suffixes in joins.

dbplyr/inst/doc/translation-function.html0000644000176200001440000032566714033043055020357 0ustar liggesusers Function translation

Function translation

There are two parts to dbplyr SQL translation: translating dplyr verbs, and translating expressions within those verbs. This vignette describes how individual expressions (function calls) are translated; vignette("translate-verb") describes how entire verbs are translated.

library(dbplyr)
library(dplyr)

dbplyr::translate_sql() powers translation of individual function calls, and I’ll use it extensively in this vignette to show what’s happening. You shouldn’t need to use it ordinary code as dbplyr takes care of the translation automatically.

translate_sql((x + y) / 2)
#> <SQL> (`x` + `y`) / 2.0

translate_sql() takes an optional con parameter. If not supplied, this causes dplyr to generate (approximately) SQL-92 compliant SQL. If supplied, dplyr uses sql_translate_env() to look up a custom environment which makes it possible for different databases to generate slightly different SQL: see vignette("new-backend") for more details. You can use the various simulate helpers to see the translations used by different backends:

translate_sql(x ^ 2L)
#> <SQL> POWER(`x`, 2)
translate_sql(x ^ 2L, con = simulate_sqlite())
#> <SQL> POWER(`x`, 2)
translate_sql(x ^ 2L, con = simulate_access())
#> <SQL> `x` ^ 2

Perfect translation is not possible because databases don’t have all the functions that R does. The goal of dplyr is to provide a semantic rather than a literal translation: what you mean, rather than precisely what is done. In fact, even for functions that exist both in databases and R, you shouldn’t expect results to be identical; database programmers have different priorities than R core programmers. For example, in R in order to get a higher level of numerical accuracy, mean() loops through the data twice. R’s mean() also provides a trim option for computing trimmed means; this is something that databases do not provide.

If you’re interested in how translate_sql() is implemented, the basic techniques that underlie the implementation of translate_sql() are described in “Advanced R”.

Basic differences

The following examples work through some of the basic differences between R and SQL.

  • " and ' mean different things

    # In SQLite variable names are escaped by double quotes:
    translate_sql(x)
    #> <SQL> `x`
    # And strings are escaped by single quotes
    translate_sql("x")
    #> <SQL> 'x'
  • And some functions have different argument orders:

    translate_sql(substr(x, 5, 10))
    #> <SQL> SUBSTR(`x`, 5, 6)
    translate_sql(log(x, 10))
    #> <SQL> LOG(10.0, `x`)
  • R and SQL have different defaults for integers and reals. In R, 1 is a real, and 1L is an integer. In SQL, 1 is an integer, and 1.0 is a real

    translate_sql(1)
    #> <SQL> 1.0
    translate_sql(1L)
    #> <SQL> 1

Known functions

Mathematics

  • basic math operators: +, -, *, /, ^
  • trigonometry: acos(), asin(), atan(), atan2(), cos(), cot(), tan(), sin()
  • hypergeometric: cosh(), coth(), sinh(), tanh()
  • logarithmic: log(), log10(), exp()
  • misc: abs(), ceiling(), sqrt(), sign(), round()

Modulo arithmetic

dbplyr translates %% to the SQL equivalents but note that it’s not precisely the same: most databases use truncated division where the modulo operator takes the sign of the dividend, where R using the mathematically preferred floored division with the modulo sign taking the sign of the divisor.

df <- tibble(
  x = c(10L, 10L, -10L, -10L), 
  y = c(3L, -3L, 3L, -3L)
)
mf <- tbl_memdb(df)

df %>% mutate(x %% y)
#> # A tibble: 4 x 3
#>       x     y `x%%y`
#>   <int> <int>  <int>
#> 1    10     3      1
#> 2    10    -3     -2
#> 3   -10     3      2
#> 4   -10    -3     -1
mf %>% mutate(x %% y)
#> # Source:   lazy query [?? x 3]
#> # Database: sqlite 3.34.1 [:memory:]
#>       x     y `x%%y`
#>   <int> <int>  <int>
#> 1    10     3      1
#> 2    10    -3      1
#> 3   -10     3     -1
#> 4   -10    -3     -1

dbplyr no longer translates %/% because there’s no robust cross-database translation available.

Logical comparisons

  • logical comparisons: <, <=, !=, >=, >, ==, %in%
  • boolean operations: &, &&, |, ||, !, xor()

Aggregation

All database provide translation for the basic aggregations: mean(), sum(), min(), max(), sd(), var(). Databases automatically drop NULLs (their equivalent of missing values) whereas in R you have to ask nicely. The aggregation functions warn you about this important difference:

translate_sql(mean(x))
#> Warning: Missing values are always removed in SQL.
#> Use `AVG(x, na.rm = TRUE)` to silence this warning
#> This warning is displayed only once per session.
#> <SQL> AVG(`x`) OVER ()
translate_sql(mean(x, na.rm = TRUE))
#> <SQL> AVG(`x`) OVER ()

Note that, by default, translate() assumes that the call is inside a mutate() or filter() and generates a window translation. If you want to see the equivalent summarise()/aggregation translation, use window = FALSE:

translate_sql(mean(x, na.rm = TRUE), window = FALSE)
#> <SQL> AVG(`x`)

Conditional evaluation

if and switch() are translate to CASE WHEN:

translate_sql(if (x > 5) "big" else "small")
#> <SQL> CASE WHEN (`x` > 5.0) THEN ('big') WHEN NOT(`x` > 5.0) THEN ('small') END
translate_sql(switch(x, a = 1L, b = 2L, 3L))
#> <SQL> CASE `x` WHEN ('a') THEN (1) WHEN ('b') THEN (2) ELSE (3) END

String manipulation

Date/time

  • string functions: tolower, toupper, trimws, nchar, substr
  • coerce types: as.numeric, as.integer, as.character

Unknown functions

Any function that dplyr doesn’t know how to convert is left as is. This means that database functions that are not covered by dplyr can be used directly via translate_sql(). Here a couple of examples that will work with SQLite:

translate_sql(glob(x, y))
#> <SQL> glob(`x`, `y`)
translate_sql(x %like% "ab%")
#> <SQL> `x` like 'ab%'

See vignette("sql") for more details.

Window functions

Things get a little trickier with window functions, because SQL’s window functions are considerably more expressive than the specific variants provided by base R or dplyr. They have the form [expression] OVER ([partition clause] [order clause] [frame_clause]):

  • The expression is a combination of variable names and window functions. Support for window functions varies from database to database, but most support the ranking functions, lead, lag, nth, first, last, count, min, max, sum, avg and stddev.

  • The partition clause specifies how the window function is broken down over groups. It plays an analogous role to GROUP BY for aggregate functions, and group_by() in dplyr. It is possible for different window functions to be partitioned into different groups, but not all databases support it, and neither does dplyr.

  • The order clause controls the ordering (when it makes a difference). This is important for the ranking functions since it specifies which variables to rank by, but it’s also needed for cumulative functions and lead. Whenever you’re thinking about before and after in SQL, you must always tell it which variable defines the order. If the order clause is missing when needed, some databases fail with an error message while others return non-deterministic results.

  • The frame clause defines which rows, or frame, that are passed to the window function, describing which rows (relative to the current row) should be included. The frame clause provides two offsets which determine the start and end of frame. There are three special values: -Inf means to include all preceding rows (in SQL, “unbounded preceding”), 0 means the current row (“current row”), and Inf means all following rows (“unbounded following”). The complete set of options is comprehensive, but fairly confusing, and is summarised visually below.

    Of the many possible specifications, there are only three that commonly used. They select between aggregation variants:

    • Recycled: BETWEEN UNBOUND PRECEEDING AND UNBOUND FOLLOWING

    • Cumulative: BETWEEN UNBOUND PRECEEDING AND CURRENT ROW

    • Rolling: BETWEEN 2 PRECEEDING AND 2 FOLLOWING

    dplyr generates the frame clause based on whether your using a recycled aggregate or a cumulative aggregate.

To see how individual window functions are translated to SQL, we can again use translate_sql():

translate_sql(mean(G))
#> <SQL> AVG(`G`) OVER ()
translate_sql(rank(G))
#> <SQL> RANK() OVER (ORDER BY `G`)
translate_sql(ntile(G, 2))
#> <SQL> NTILE(2) OVER (ORDER BY `G`)
translate_sql(lag(G))
#> <SQL> LAG(`G`, 1, NULL) OVER ()

If the tbl has been grouped or arranged previously in the pipeline, then dplyr will use that information to set the “partition by” and “order by” clauses. For interactive exploration, you can achieve the same effect by setting the vars_group and vars_order arguments to translate_sql()

translate_sql(cummean(G), vars_order = "year")
#> <SQL> AVG(`G`) OVER (ORDER BY `year` ROWS UNBOUNDED PRECEDING)
translate_sql(rank(), vars_group = "ID")
#> <SQL> RANK() OVER (PARTITION BY `ID`)

There are some challenges when translating window functions between R and SQL, because dplyr tries to keep the window functions as similar as possible to both the existing R analogues and to the SQL functions. This means that there are three ways to control the order clause depending on which window function you’re using:

  • For ranking functions, the ordering variable is the first argument: rank(x), ntile(y, 2). If omitted or NULL, will use the default ordering associated with the tbl (as set by arrange()).

  • Accumulating aggregates only take a single argument (the vector to aggregate). To control ordering, use order_by().

  • Aggregates implemented in dplyr (lead, lag, nth_value, first_value, last_value) have an order_by argument. Supply it to override the default ordering.

The three options are illustrated in the snippet below:

mutate(players,
  min_rank(yearID),
  order_by(yearID, cumsum(G)),
  lead(G, order_by = yearID)
)

Currently there is no way to order by multiple variables, except by setting the default ordering with arrange(). This will be added in a future release.

dbplyr/inst/doc/dbplyr.R0000644000176200001440000000420014033043053014676 0ustar liggesusers## ---- include = FALSE--------------------------------------------------------- knitr::opts_chunk$set(collapse = TRUE, comment = "#>") options(tibble.print_min = 6L, tibble.print_max = 6L, digits = 3) ## ---- eval = FALSE------------------------------------------------------------ # install.packages("dbplyr") ## ----setup, message = FALSE--------------------------------------------------- library(dplyr) con <- DBI::dbConnect(RSQLite::SQLite(), dbname = ":memory:") ## ---- eval = FALSE------------------------------------------------------------ # con <- DBI::dbConnect(RMariaDB::MariaDB(), # host = "database.rstudio.com", # user = "hadley", # password = rstudioapi::askForPassword("Database password") # ) ## ----------------------------------------------------------------------------- copy_to(con, nycflights13::flights, "flights", temporary = FALSE, indexes = list( c("year", "month", "day"), "carrier", "tailnum", "dest" ) ) ## ----------------------------------------------------------------------------- flights_db <- tbl(con, "flights") ## ----------------------------------------------------------------------------- flights_db ## ----------------------------------------------------------------------------- flights_db %>% select(year:day, dep_delay, arr_delay) flights_db %>% filter(dep_delay > 240) flights_db %>% group_by(dest) %>% summarise(delay = mean(dep_time)) ## ----------------------------------------------------------------------------- tailnum_delay_db <- flights_db %>% group_by(tailnum) %>% summarise( delay = mean(arr_delay), n = n() ) %>% arrange(desc(delay)) %>% filter(n > 100) ## ----------------------------------------------------------------------------- tailnum_delay_db ## ----------------------------------------------------------------------------- tailnum_delay_db %>% show_query() ## ----------------------------------------------------------------------------- tailnum_delay <- tailnum_delay_db %>% collect() tailnum_delay ## ---- error = TRUE------------------------------------------------------------ nrow(tailnum_delay_db) tail(tailnum_delay_db) dbplyr/inst/doc/reprex.R0000644000176200001440000000213714033043054014717 0ustar liggesusers## ---- include = FALSE--------------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ## ----setup, message = FALSE--------------------------------------------------- library(dplyr) library(dbplyr) ## ----------------------------------------------------------------------------- mf <- memdb_frame(g = c(1, 1, 2, 2, 2), x = 1:5, y = 5:1) mf mf %>% group_by(g) %>% summarise_all(mean, na.rm = TRUE) ## ----------------------------------------------------------------------------- mtcars_db <- tbl_memdb(mtcars) mtcars_db %>% group_by(cyl) %>% summarise(n = n()) %>% show_query() ## ----------------------------------------------------------------------------- x <- c("abc", "def", "ghif") lazy_frame(x = x, con = simulate_postgres()) %>% head(5) %>% show_query() lazy_frame(x = x, con = simulate_mssql()) %>% head(5) %>% show_query() ## ----------------------------------------------------------------------------- translate_sql(substr(x, 1, 2), con = simulate_postgres()) translate_sql(substr(x, 1, 2), con = simulate_sqlite()) dbplyr/inst/doc/backend-2.R0000644000176200001440000000050614033043047015140 0ustar liggesusers## ---- include = FALSE--------------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ## ----------------------------------------------------------------------------- #' @importFrom dbplyr dbplyr_edition #' @export dbplyr_edition.myConnectionClass <- function(con) 2L dbplyr/inst/doc/reprex.html0000644000176200001440000005254614033043054015473 0ustar liggesusers Reprexes for dbplyr

Reprexes for dbplyr

If you’re reporting a bug in dbplyr, it is much easier for me to help you if you can supply a reprex that I can run on my computer. Creating reprexes for dbplyr is particularly challenging because you are probably using a database that you can’t share with me. Fortunately, in many cases you can still demonstrate the problem even if I don’t have the complete dataset, or even access to the database system that you’re using.

This vignette outlines three approaches for creating reprexes that will work anywhere:

  • Use memdb_frame()/tbl_memdb() to easily create datasets that live in an in-memory SQLite database.

  • Use lazy_frame()/tbl_lazy() to simulate SQL generation of dplyr pipelines.

  • Use translate_sql() to simulate SQL generation of columnar expression.

library(dplyr)
library(dbplyr)

Using memdb_frame()

The first place to start is with SQLite. SQLite is particularly appealing because it’s completely embedded instead an R package so doesn’t have any external dependencies. SQLite is designed to be small and simple, so it can’t demonstrate all problems, but it’s easy to try out and a great place to start.

You can easily create a SQLite in-memory database table using memdb_frame():

mf <- memdb_frame(g = c(1, 1, 2, 2, 2), x = 1:5, y = 5:1)
mf
#> # Source:   table<dbplyr_001> [?? x 3]
#> # Database: sqlite 3.34.1 [:memory:]
#>       g     x     y
#>   <dbl> <int> <int>
#> 1     1     1     5
#> 2     1     2     4
#> 3     2     3     3
#> 4     2     4     2
#> # … with more rows

mf %>% 
  group_by(g) %>% 
  summarise_all(mean, na.rm = TRUE)
#> # Source:   lazy query [?? x 3]
#> # Database: sqlite 3.34.1 [:memory:]
#>       g     x     y
#>   <dbl> <dbl> <dbl>
#> 1     1   1.5   4.5
#> 2     2   4     2

Reprexes are easiest to understand if you create very small custom data, but if you do want to use an existing data frame you can use tbl_memdb():

mtcars_db <- tbl_memdb(mtcars)
mtcars_db %>% 
  group_by(cyl) %>% 
  summarise(n = n()) %>% 
  show_query()
#> <SQL>
#> SELECT `cyl`, COUNT(*) AS `n`
#> FROM `mtcars`
#> GROUP BY `cyl`

Translating verbs

Many problems with dbplyr come down to incorrect SQL generation. Fortunately, it’s possible to generate SQL without a database using lazy_frame() and tbl_lazy(). Both take an con argument which takes a database “simulator” like simulate_postgres(), simulate_sqlite(), etc.

x <- c("abc", "def", "ghif")

lazy_frame(x = x, con = simulate_postgres()) %>% 
  head(5) %>% 
  show_query()
#> <SQL>
#> SELECT *
#> FROM `df`
#> LIMIT 5

lazy_frame(x = x, con = simulate_mssql()) %>% 
  head(5) %>% 
  show_query()
#> <SQL>
#> SELECT TOP 5 *
#> FROM `df`

If you isolate the problem to incorrect SQL generation, it would be very helpful if you could also suggest more appropriate SQL.

Translating individual expressions

In some cases, you might be able to track the problem down to incorrect translation for a single column expression. In that case, you can make your reprex even simpler with translate_sql():

translate_sql(substr(x, 1, 2), con = simulate_postgres())
#> <SQL> SUBSTR(`x`, 1, 2)
translate_sql(substr(x, 1, 2), con = simulate_sqlite())
#> <SQL> SUBSTR(`x`, 1, 2)
dbplyr/inst/doc/dbplyr.Rmd0000644000176200001440000002626314002647450015243 0ustar liggesusers--- title: "Introduction to dbplyr" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Introduction to dbplyr} %\VignetteEngine{knitr::rmarkdown} \usepackage[utf8]{inputenc} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") options(tibble.print_min = 6L, tibble.print_max = 6L, digits = 3) ``` As well as working with local in-memory data stored in data frames, dplyr also works with remote on-disk data stored in databases. This is particularly useful in two scenarios: * Your data is already in a database. * You have so much data that it does not all fit into memory simultaneously and you need to use some external storage engine. (If your data fits in memory there is no advantage to putting it in a database: it will only be slower and more frustrating.) This vignette focuses on the first scenario because it's the most common. If you're using R to do data analysis inside a company, most of the data you need probably already lives in a database (it's just a matter of figuring out which one!). However, you will learn how to load data in to a local database in order to demonstrate dplyr's database tools. At the end, I'll also give you a few pointers if you do need to set up your own database. ## Getting started To use databases with dplyr you need to first install dbplyr: ```{r, eval = FALSE} install.packages("dbplyr") ``` You'll also need to install a DBI backend package. The DBI package provides a common interface that allows dplyr to work with many different databases using the same code. DBI is automatically installed with dbplyr, but you need to install a specific backend for the database that you want to connect to. Five commonly used backends are: * [RMariaDB](https://CRAN.R-project.org/package=RMariaDB) connects to MySQL and MariaDB * [RPostgres](https://CRAN.R-project.org/package=RPostgres) connects to Postgres and Redshift. * [RSQLite](https://github.com/r-dbi/RSQLite) embeds a SQLite database. * [odbc](https://github.com/r-dbi/odbc#odbc) connects to many commercial databases via the open database connectivity protocol. * [bigrquery](https://github.com/r-dbi/bigrquery) connects to Google's BigQuery. If the database you need to connect to is not listed here, you'll need to do some investigation (i.e. googling) yourself. In this vignette, we're going to use the RSQLite backend which is automatically installed when you install dbplyr. SQLite is a great way to get started with databases because it's completely embedded inside an R package. Unlike most other systems, you don't need to setup a separate database server. SQLite is great for demos, but is surprisingly powerful, and with a little practice you can use it to easily work with many gigabytes of data. ## Connecting to the database To work with a database in dplyr, you must first connect to it, using `DBI::dbConnect()`. We're not going to go into the details of the DBI package here, but it's the foundation upon which dbplyr is built. You'll need to learn more about if you need to do things to the database that are beyond the scope of dplyr. ```{r setup, message = FALSE} library(dplyr) con <- DBI::dbConnect(RSQLite::SQLite(), dbname = ":memory:") ``` The arguments to `DBI::dbConnect()` vary from database to database, but the first argument is always the database backend. It's `RSQLite::SQLite()` for RSQLite, `RMariaDB::MariaDB()` for RMariaDB, `RPostgres::Postgres()` for RPostgres, `odbc::odbc()` for odbc, and `bigrquery::bigquery()` for BigQuery. SQLite only needs one other argument: the path to the database. Here we use the special string `":memory:"` which causes SQLite to make a temporary in-memory database. Most existing databases don't live in a file, but instead live on another server. That means in real-life that your code will look more like this: ```{r, eval = FALSE} con <- DBI::dbConnect(RMariaDB::MariaDB(), host = "database.rstudio.com", user = "hadley", password = rstudioapi::askForPassword("Database password") ) ``` (If you're not using RStudio, you'll need some other way to securely retrieve your password. You should never record it in your analysis scripts or type it into the console. [Securing Credentials](https://db.rstudio.com/best-practices/managing-credentials) provides some best practices.) Our temporary database has no data in it, so we'll start by copying over `nycflights13::flights` using the convenient `copy_to()` function. This is a quick and dirty way of getting data into a database and is useful primarily for demos and other small jobs. ```{r} copy_to(con, nycflights13::flights, "flights", temporary = FALSE, indexes = list( c("year", "month", "day"), "carrier", "tailnum", "dest" ) ) ``` As you can see, the `copy_to()` operation has an additional argument that allows you to supply indexes for the table. Here we set up indexes that will allow us to quickly process the data by day, carrier, plane, and destination. Creating the right indices is key to good database performance, but is unfortunately beyond the scope of this article. Now that we've copied the data, we can use `tbl()` to take a reference to it: ```{r} flights_db <- tbl(con, "flights") ``` When you print it out, you'll notice that it mostly looks like a regular tibble: ```{r} flights_db ``` The main difference is that you can see that it's a remote source in a SQLite database. ## Generating queries To interact with a database you usually use SQL, the Structured Query Language. SQL is over 40 years old, and is used by pretty much every database in existence. The goal of dbplyr is to automatically generate SQL for you so that you're not forced to use it. However, SQL is a very large language and dbplyr doesn't do everything. It focusses on `SELECT` statements, the SQL you write most often as an analyst. Most of the time you don't need to know anything about SQL, and you can continue to use the dplyr verbs that you're already familiar with: ```{r} flights_db %>% select(year:day, dep_delay, arr_delay) flights_db %>% filter(dep_delay > 240) flights_db %>% group_by(dest) %>% summarise(delay = mean(dep_time)) ``` However, in the long-run, I highly recommend you at least learn the basics of SQL. It's a valuable skill for any data scientist, and it will help you debug problems if you run into problems with dplyr's automatic translation. If you're completely new to SQL you might start with this [codeacademy tutorial](https://www.codecademy.com/learn/learn-sql). If you have some familiarity with SQL and you'd like to learn more, I found [how indexes work in SQLite](https://www.sqlite.org/queryplanner.html) and [10 easy steps to a complete understanding of SQL](https://blog.jooq.org/2016/03/17/10-easy-steps-to-a-complete-understanding-of-sql/) to be particularly helpful. The most important difference between ordinary data frames and remote database queries is that your R code is translated into SQL and executed in the database on the remote server, not in R on your local machine. When working with databases, dplyr tries to be as lazy as possible: * It never pulls data into R unless you explicitly ask for it. * It delays doing any work until the last possible moment: it collects together everything you want to do and then sends it to the database in one step. For example, take the following code: ```{r} tailnum_delay_db <- flights_db %>% group_by(tailnum) %>% summarise( delay = mean(arr_delay), n = n() ) %>% arrange(desc(delay)) %>% filter(n > 100) ``` Surprisingly, this sequence of operations never touches the database. It's not until you ask for the data (e.g. by printing `tailnum_delay`) that dplyr generates the SQL and requests the results from the database. Even then it tries to do as little work as possible and only pulls down a few rows. ```{r} tailnum_delay_db ``` Behind the scenes, dplyr is translating your R code into SQL. You can see the SQL it's generating with `show_query()`: ```{r} tailnum_delay_db %>% show_query() ``` If you're familiar with SQL, this probably isn't exactly what you'd write by hand, but it does the job. You can learn more about the SQL translation in `vignette("translation-verb")` and `vignette("translation-function")`. Typically, you'll iterate a few times before you figure out what data you need from the database. Once you've figured it out, use `collect()` to pull all the data down into a local tibble: ```{r} tailnum_delay <- tailnum_delay_db %>% collect() tailnum_delay ``` `collect()` requires that database does some work, so it may take a long time to complete. Otherwise, dplyr tries to prevent you from accidentally performing expensive query operations: * Because there's generally no way to determine how many rows a query will return unless you actually run it, `nrow()` is always `NA`. * Because you can't find the last few rows without executing the whole query, you can't use `tail()`. ```{r, error = TRUE} nrow(tailnum_delay_db) tail(tailnum_delay_db) ``` You can also ask the database how it plans to execute the query with `explain()`. The output is database dependent, and can be esoteric, but learning a bit about it can be very useful because it helps you understand if the database can execute the query efficiently, or if you need to create new indices. ## Creating your own database If you don't already have a database, here's some advice from my experiences setting up and running all of them. SQLite is by far the easiest to get started with. PostgreSQL is not too much harder to use and has a wide range of built-in functions. In my opinion, you shouldn't bother with MySQL/MariaDB: it's a pain to set up, the documentation is subpar, and it's less featureful than Postgres. Google BigQuery might be a good fit if you have very large data, or if you're willing to pay (a small amount of) money to someone who'll look after your database. All of these databases follow a client-server model - a computer that connects to the database and the computer that is running the database (the two may be one and the same but usually isn't). Getting one of these databases up and running is beyond the scope of this article, but there are plenty of tutorials available on the web. ### MySQL/MariaDB In terms of functionality, MySQL lies somewhere between SQLite and PostgreSQL. It provides a wider range of [built-in functions](http://dev.mysql.com/doc/refman/5.0/en/functions.html). It gained support for window functions in 2018. ### PostgreSQL PostgreSQL is a considerably more powerful database than SQLite. It has a much wider range of [built-in functions](http://www.postgresql.org/docs/current/static/functions.html), and is generally a more featureful database. ### BigQuery BigQuery is a hosted database server provided by Google. To connect, you need to provide your `project`, `dataset` and optionally a project for `billing` (if billing for `project` isn't enabled). It provides a similar set of functions to Postgres and is designed specifically for analytic workflows. Because it's a hosted solution, there's no setup involved, but if you have a lot of data, getting it to Google can be an ordeal (especially because upload support from R is not great currently). (If you have lots of data, you can [ship hard drives](https://cloud.google.com/products/data-transfer)!) dbplyr/inst/doc/reprex.Rmd0000644000176200001440000000564014002647450015250 0ustar liggesusers--- title: "Reprexes for dbplyr" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Reprexes for dbplyr} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` If you're reporting a bug in dbplyr, it is much easier for me to help you if you can supply a [reprex](https://reprex.tidyverse.org) that I can run on my computer. Creating reprexes for dbplyr is particularly challenging because you are probably using a database that you can't share with me. Fortunately, in many cases you can still demonstrate the problem even if I don't have the complete dataset, or even access to the database system that you're using. This vignette outlines three approaches for creating reprexes that will work anywhere: * Use `memdb_frame()`/`tbl_memdb()` to easily create datasets that live in an in-memory SQLite database. * Use `lazy_frame()`/`tbl_lazy()` to simulate SQL generation of dplyr pipelines. * Use `translate_sql()` to simulate SQL generation of columnar expression. ```{r setup, message = FALSE} library(dplyr) library(dbplyr) ``` ## Using `memdb_frame()` The first place to start is with SQLite. SQLite is particularly appealing because it's completely embedded instead an R package so doesn't have any external dependencies. SQLite is designed to be small and simple, so it can't demonstrate all problems, but it's easy to try out and a great place to start. You can easily create a SQLite in-memory database table using `memdb_frame()`: ```{r} mf <- memdb_frame(g = c(1, 1, 2, 2, 2), x = 1:5, y = 5:1) mf mf %>% group_by(g) %>% summarise_all(mean, na.rm = TRUE) ``` Reprexes are easiest to understand if you create very small custom data, but if you do want to use an existing data frame you can use `tbl_memdb()`: ```{r} mtcars_db <- tbl_memdb(mtcars) mtcars_db %>% group_by(cyl) %>% summarise(n = n()) %>% show_query() ``` ## Translating verbs Many problems with dbplyr come down to incorrect SQL generation. Fortunately, it's possible to generate SQL without a database using `lazy_frame()` and `tbl_lazy()`. Both take an `con` argument which takes a database "simulator" like `simulate_postgres()`, `simulate_sqlite()`, etc. ```{r} x <- c("abc", "def", "ghif") lazy_frame(x = x, con = simulate_postgres()) %>% head(5) %>% show_query() lazy_frame(x = x, con = simulate_mssql()) %>% head(5) %>% show_query() ``` If you isolate the problem to incorrect SQL generation, it would be very helpful if you could also suggest more appropriate SQL. ## Translating individual expressions In some cases, you might be able to track the problem down to incorrect translation for a single column expression. In that case, you can make your reprex even simpler with `translate_sql()`: ```{r} translate_sql(substr(x, 1, 2), con = simulate_postgres()) translate_sql(substr(x, 1, 2), con = simulate_sqlite()) ``` dbplyr/inst/doc/translation-function.Rmd0000644000176200001440000002316714002647450020130 0ustar liggesusers--- title: "Function translation" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Function translation} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") options(tibble.print_min = 4L, tibble.print_max = 4L) ``` There are two parts to dbplyr SQL translation: translating dplyr verbs, and translating expressions within those verbs. This vignette describes how individual expressions (function calls) are translated; `vignette("translate-verb")` describes how entire verbs are translated. ```{r, message = FALSE} library(dbplyr) library(dplyr) ``` `dbplyr::translate_sql()` powers translation of individual function calls, and I'll use it extensively in this vignette to show what's happening. You shouldn't need to use it ordinary code as dbplyr takes care of the translation automatically. ```{r} translate_sql((x + y) / 2) ``` `translate_sql()` takes an optional `con` parameter. If not supplied, this causes dplyr to generate (approximately) SQL-92 compliant SQL. If supplied, dplyr uses `sql_translate_env()` to look up a custom environment which makes it possible for different databases to generate slightly different SQL: see `vignette("new-backend")` for more details. You can use the various simulate helpers to see the translations used by different backends: ```{r} translate_sql(x ^ 2L) translate_sql(x ^ 2L, con = simulate_sqlite()) translate_sql(x ^ 2L, con = simulate_access()) ``` Perfect translation is not possible because databases don't have all the functions that R does. The goal of dplyr is to provide a semantic rather than a literal translation: what you mean, rather than precisely what is done. In fact, even for functions that exist both in databases and R, you shouldn't expect results to be identical; database programmers have different priorities than R core programmers. For example, in R in order to get a higher level of numerical accuracy, `mean()` loops through the data twice. R's `mean()` also provides a `trim` option for computing trimmed means; this is something that databases do not provide. If you're interested in how `translate_sql()` is implemented, the basic techniques that underlie the implementation of `translate_sql()` are described in ["Advanced R"](https://adv-r.hadley.nz/translation.html). ## Basic differences The following examples work through some of the basic differences between R and SQL. * `"` and `'` mean different things ```{r} # In SQLite variable names are escaped by double quotes: translate_sql(x) # And strings are escaped by single quotes translate_sql("x") ``` * And some functions have different argument orders: ```{r} translate_sql(substr(x, 5, 10)) translate_sql(log(x, 10)) ``` * R and SQL have different defaults for integers and reals. In R, 1 is a real, and 1L is an integer. In SQL, 1 is an integer, and 1.0 is a real ```{r} translate_sql(1) translate_sql(1L) ``` ## Known functions ### Mathematics * basic math operators: `+`, `-`, `*`, `/`, `^` * trigonometry: `acos()`, `asin()`, `atan()`, `atan2()`, `cos()`, `cot()`, `tan()`, `sin()` * hypergeometric: `cosh()`, `coth()`, `sinh()`, `tanh()` * logarithmic: `log()`, `log10()`, `exp()` * misc: `abs()`, `ceiling()`, `sqrt()`, `sign()`, `round()` ## Modulo arithmetic dbplyr translates `%%` to the SQL equivalents but note that it's not precisely the same: most databases use truncated division where the modulo operator takes the sign of the dividend, where R using the mathematically preferred floored division with the modulo sign taking the sign of the divisor. ```{r} df <- tibble( x = c(10L, 10L, -10L, -10L), y = c(3L, -3L, 3L, -3L) ) mf <- tbl_memdb(df) df %>% mutate(x %% y) mf %>% mutate(x %% y) ``` dbplyr no longer translates `%/%` because there's no robust cross-database translation available. ### Logical comparisons * logical comparisons: `<`, `<=`, `!=`, `>=`, `>`, `==`, `%in%` * boolean operations: `&`, `&&`, `|`, `||`, `!`, `xor()` ### Aggregation All database provide translation for the basic aggregations: `mean()`, `sum()`, `min()`, `max()`, `sd()`, `var()`. Databases automatically drop NULLs (their equivalent of missing values) whereas in R you have to ask nicely. The aggregation functions warn you about this important difference: ```{r} translate_sql(mean(x)) translate_sql(mean(x, na.rm = TRUE)) ``` Note that, by default, `translate()` assumes that the call is inside a `mutate()` or `filter()` and generates a window translation. If you want to see the equivalent `summarise()`/aggregation translation, use `window = FALSE`: ```{r} translate_sql(mean(x, na.rm = TRUE), window = FALSE) ``` ### Conditional evaluation `if` and `switch()` are translate to `CASE WHEN`: ```{r} translate_sql(if (x > 5) "big" else "small") translate_sql(switch(x, a = 1L, b = 2L, 3L)) ``` ### String manipulation ### Date/time * string functions: `tolower`, `toupper`, `trimws`, `nchar`, `substr` * coerce types: `as.numeric`, `as.integer`, `as.character` ## Unknown functions Any function that dplyr doesn't know how to convert is left as is. This means that database functions that are not covered by dplyr can be used directly via `translate_sql()`. Here a couple of examples that will work with [SQLite](https://www.sqlite.org/lang_corefunc.html): ```{r} translate_sql(glob(x, y)) translate_sql(x %like% "ab%") ``` See `vignette("sql")` for more details. ## Window functions Things get a little trickier with window functions, because SQL's window functions are considerably more expressive than the specific variants provided by base R or dplyr. They have the form `[expression] OVER ([partition clause] [order clause] [frame_clause])`: * The __expression__ is a combination of variable names and window functions. Support for window functions varies from database to database, but most support the ranking functions, `lead`, `lag`, `nth`, `first`, `last`, `count`, `min`, `max`, `sum`, `avg` and `stddev`. * The __partition clause__ specifies how the window function is broken down over groups. It plays an analogous role to `GROUP BY` for aggregate functions, and `group_by()` in dplyr. It is possible for different window functions to be partitioned into different groups, but not all databases support it, and neither does dplyr. * The __order clause__ controls the ordering (when it makes a difference). This is important for the ranking functions since it specifies which variables to rank by, but it's also needed for cumulative functions and lead. Whenever you're thinking about before and after in SQL, you must always tell it which variable defines the order. If the order clause is missing when needed, some databases fail with an error message while others return non-deterministic results. * The __frame clause__ defines which rows, or __frame__, that are passed to the window function, describing which rows (relative to the current row) should be included. The frame clause provides two offsets which determine the start and end of frame. There are three special values: -Inf means to include all preceding rows (in SQL, "unbounded preceding"), 0 means the current row ("current row"), and Inf means all following rows ("unbounded following"). The complete set of options is comprehensive, but fairly confusing, and is summarised visually below. ```{r echo = FALSE, out.width = "100%"} knitr::include_graphics("windows.png", dpi = 300) ``` Of the many possible specifications, there are only three that commonly used. They select between aggregation variants: * Recycled: `BETWEEN UNBOUND PRECEEDING AND UNBOUND FOLLOWING` * Cumulative: `BETWEEN UNBOUND PRECEEDING AND CURRENT ROW` * Rolling: `BETWEEN 2 PRECEEDING AND 2 FOLLOWING` dplyr generates the frame clause based on whether your using a recycled aggregate or a cumulative aggregate. To see how individual window functions are translated to SQL, we can again use `translate_sql()`: ```{r} translate_sql(mean(G)) translate_sql(rank(G)) translate_sql(ntile(G, 2)) translate_sql(lag(G)) ``` If the tbl has been grouped or arranged previously in the pipeline, then dplyr will use that information to set the "partition by" and "order by" clauses. For interactive exploration, you can achieve the same effect by setting the `vars_group` and `vars_order` arguments to `translate_sql()` ```{r} translate_sql(cummean(G), vars_order = "year") translate_sql(rank(), vars_group = "ID") ``` There are some challenges when translating window functions between R and SQL, because dplyr tries to keep the window functions as similar as possible to both the existing R analogues and to the SQL functions. This means that there are three ways to control the order clause depending on which window function you're using: * For ranking functions, the ordering variable is the first argument: `rank(x)`, `ntile(y, 2)`. If omitted or `NULL`, will use the default ordering associated with the tbl (as set by `arrange()`). * Accumulating aggregates only take a single argument (the vector to aggregate). To control ordering, use `order_by()`. * Aggregates implemented in dplyr (`lead`, `lag`, `nth_value`, `first_value`, `last_value`) have an `order_by` argument. Supply it to override the default ordering. The three options are illustrated in the snippet below: ```{r, eval = FALSE} mutate(players, min_rank(yearID), order_by(yearID, cumsum(G)), lead(G, order_by = yearID) ) ``` Currently there is no way to order by multiple variables, except by setting the default ordering with `arrange()`. This will be added in a future release. dbplyr/inst/doc/translation-function.R0000644000176200001440000000501014033043055017565 0ustar liggesusers## ----setup, include = FALSE--------------------------------------------------- knitr::opts_chunk$set(collapse = TRUE, comment = "#>") options(tibble.print_min = 4L, tibble.print_max = 4L) ## ---- message = FALSE--------------------------------------------------------- library(dbplyr) library(dplyr) ## ----------------------------------------------------------------------------- translate_sql((x + y) / 2) ## ----------------------------------------------------------------------------- translate_sql(x ^ 2L) translate_sql(x ^ 2L, con = simulate_sqlite()) translate_sql(x ^ 2L, con = simulate_access()) ## ----------------------------------------------------------------------------- # In SQLite variable names are escaped by double quotes: translate_sql(x) # And strings are escaped by single quotes translate_sql("x") ## ----------------------------------------------------------------------------- translate_sql(substr(x, 5, 10)) translate_sql(log(x, 10)) ## ----------------------------------------------------------------------------- translate_sql(1) translate_sql(1L) ## ----------------------------------------------------------------------------- df <- tibble( x = c(10L, 10L, -10L, -10L), y = c(3L, -3L, 3L, -3L) ) mf <- tbl_memdb(df) df %>% mutate(x %% y) mf %>% mutate(x %% y) ## ----------------------------------------------------------------------------- translate_sql(mean(x)) translate_sql(mean(x, na.rm = TRUE)) ## ----------------------------------------------------------------------------- translate_sql(mean(x, na.rm = TRUE), window = FALSE) ## ----------------------------------------------------------------------------- translate_sql(if (x > 5) "big" else "small") translate_sql(switch(x, a = 1L, b = 2L, 3L)) ## ----------------------------------------------------------------------------- translate_sql(glob(x, y)) translate_sql(x %like% "ab%") ## ----echo = FALSE, out.width = "100%"----------------------------------------- knitr::include_graphics("windows.png", dpi = 300) ## ----------------------------------------------------------------------------- translate_sql(mean(G)) translate_sql(rank(G)) translate_sql(ntile(G, 2)) translate_sql(lag(G)) ## ----------------------------------------------------------------------------- translate_sql(cummean(G), vars_order = "year") translate_sql(rank(), vars_group = "ID") ## ---- eval = FALSE------------------------------------------------------------ # mutate(players, # min_rank(yearID), # order_by(yearID, cumsum(G)), # lead(G, order_by = yearID) # ) dbplyr/inst/doc/backend-2.Rmd0000644000176200001440000001042114031447261015462 0ustar liggesusers--- title: "dplyr 2.0.0 backend API" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{dplyr 2.0.0 backend API} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` This transition guide is aimed at backend authors. dbplyr 2.0.0 is an important release for backends because it starts the process of moving all backend generics into dbplyr (instead of some living in dplyr). This move has been designed to occur in phases to avoid sudden breakages and give backend authors plenty of time to make changes. The current timeline is something like this: - dbplyr 2.0.0 adds a new interface for database backends. The old interface remains so all existing backends continue to work, but new packages should use the new interface, and existing backends should start the update process.  - dbplyr 2.1.0 (to be released \>= 6 months after dbplyr 2.0.0) deprecates the old interface, so that users are encouraged to upgrade backends. - dbplyr 2.2.0 (to be released \>= 12 months after dbplyr 2.0.0) removes the old interface so user must upgrade backends. - A future version of dplyr will deprecate then remove the database generics. ## Unused generics A number of generics are no longer used so you can delete the corresponding methods: - `db_write_table()` calls `DBI::dbWriteTable()` instead of nine individual generics: `db_create_indexes()`, `db_begin()`, `db_rollback()`, `db_commit()`, `db_list_tables()`, `db_drop_table()`, `db_has_table()`, `db_create_table()`, and `db_data_types()`. - `sql_escape_ident()` and `sql_escape_string()` are no longer used in favour of calling `dbQuoteIdentifier()` and `dbQuoteString()` directly. - `db_query_rows()` was never actually used. Making these changes are important because they ensure your backend works consistently whether you use it through DBI or dplyr. ## 2nd edition dbplyr 2.0.0 draws inspiration from the idea of an [edition](https://testthat.r-lib.org/articles/third-edition.html) so that to tell dbplyr to use the new generics, you need to do two things: - Depend on dbplyr 2.0.0 in your `DESCRIPTION`, e.g. `Imports: dbplyr (>= 2.0.0)`. This ensures that when someone installs your package they get the latest version of dbplyr. - Provide a method for the `dbplyr_edition` generic: ```{r} #' @importFrom dbplyr dbplyr_edition #' @export dbplyr_edition.myConnectionClass <- function(con) 2L ``` This tells dbplyr to use the new generics instead of the old generics. Then you'll need to update your methods, following the advice below. ## SQL generation There are a number of dplyr generics that generate then execute SQL. These have been replaced by dbplyr generics that just generate the SQL (and dbplyr takes care of executing it): - `dplyr::db_analyze()` -\> `dbplyr::sql_table_analyze()` - `dplyr::db_create_index()` -\> `dbplyr::sql_table_index()` - `dplyr::db_explain()` -\> `dbplyr::sql_query_explain()` - `dplyr::db_query_fields()` -\> `dbplyr::sql_query_fields()` - `dplyr::db_save_query()` -\> `dbplyr::sql_query_save()` If you have methods for any of those generics, you'll need to extract the SQL generation code into a new `sql_` method. ## Renamed generics A number of other generics have been renamed: - `dplyr::sql_select()` -\> `dbplyr::sql_query_select()` - `dplyr::sql_join()` -\> `dbplyr::sql_query_join()` - `dplyr::sql_semi_join()` -\> `dbplyr::sql_query_semi_join()` - `dplyr::sql_set_op()` -\> `dbplyr::sql_query_set_op()` - `dplyr::sql_subquery()` -\> `dbplyr::sql_query_wrap()` - `dplyr::sql_translate_env()` -\> `dbplyr::sql_translation()` - `dplyr::db_desc()` -\> `dbplyr::db_connection_describe()` If you have methods for any of these generics, you'll need to rename. ## New generics You may also want to consider methods for the new generics in dbplyr 2.0.0: - Provide a method for `db_temporary_table()` if your backend requires that temporary tables have special names. - Provide a method for `sql_expr_matches()` if your database has special syntax for matching two values (see ). - Provide a method for `sql_join_suffix()` if your backend can't use the usual `.x` and `.y` suffixes in joins. dbplyr/inst/doc/translation-verb.Rmd0000644000176200001440000000772014002647450017236 0ustar liggesusers--- title: "Verb translation" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Verb translation} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- There are two parts to dbplyr SQL translation: translating dplyr verbs, and translating expressions within those verbs. This vignette describes how entire verbs are translated; `vignette("translate-function")` describes how individual expressions within those verbs are translated. All dplyr verbs generate a `SELECT` statement. To demonstrate we'll make a temporary database with a couple of tables ```{r, message = FALSE} library(dplyr) con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") flights <- copy_to(con, nycflights13::flights) airports <- copy_to(con, nycflights13::airports) ``` ## Single table verbs * `select()` and `mutate()` modify the `SELECT` clause: ```{r} flights %>% select(contains("delay")) %>% show_query() flights %>% select(distance, air_time) %>% mutate(speed = distance / (air_time / 60)) %>% show_query() ``` (As you can see here, the generated SQL isn't always as minimal as you might generate by hand.) * `filter()` generates a `WHERE` clause: ```{r} flights %>% filter(month == 1, day == 1) %>% show_query() ``` * `arrange()` generates an `ORDER BY` clause: ```{r} flights %>% arrange(carrier, desc(arr_delay)) %>% show_query() ``` * `summarise()` and `group_by()` work together to generate a `GROUP BY` clause: ```{r} flights %>% group_by(month, day) %>% summarise(delay = mean(dep_delay)) %>% show_query() ``` ## Dual table verbs | R | SQL |------------------|------------------------------------------------------------ | `inner_join()` | `SELECT * FROM x JOIN y ON x.a = y.a` | `left_join()` | `SELECT * FROM x LEFT JOIN y ON x.a = y.a` | `right_join()` | `SELECT * FROM x RIGHT JOIN y ON x.a = y.a` | `full_join()` | `SELECT * FROM x FULL JOIN y ON x.a = y.a` | `semi_join()` | `SELECT * FROM x WHERE EXISTS (SELECT 1 FROM y WHERE x.a = y.a)` | `anti_join()` | `SELECT * FROM x WHERE NOT EXISTS (SELECT 1 FROM y WHERE x.a = y.a)` | `intersect(x, y)`| `SELECT * FROM x INTERSECT SELECT * FROM y` | `union(x, y)` | `SELECT * FROM x UNION SELECT * FROM y` | `setdiff(x, y)` | `SELECT * FROM x EXCEPT SELECT * FROM y` `x` and `y` don't have to be tables in the same database. If you specify `copy = TRUE`, dplyr will copy the `y` table into the same location as the `x` variable. This is useful if you've downloaded a summarised dataset and determined a subset of interest that you now want the full data for. You can use `semi_join(x, y, copy = TRUE)` to upload the indices of interest to a temporary table in the same database as `x`, and then perform a efficient semi join in the database. If you're working with large data, it maybe also be helpful to set `auto_index = TRUE`. That will automatically add an index on the join variables to the temporary table. ## Behind the scenes The verb level SQL translation is implemented on top of `tbl_lazy`, which basically tracks the operations you perform in a pipeline (see `lazy-ops.R`). Turning that into a SQL query takes place in three steps: * `sql_build()` recurses over the lazy op data structure building up query objects (`select_query()`, `join_query()`, `set_op_query()` etc) that represent the different subtypes of `SELECT` queries that we might generate. * `sql_optimise()` takes a pass over these SQL objects, looking for potential optimisations. Currently this only involves removing subqueries where possible. * `sql_render()` calls an SQL generation function (`sql_query_select()`, `sql_query_join()`, `sql_query_semi_join()`, `sql_query_set_op()`, ...) to produce the actual SQL. Each of these functions is a generic, taking the connection as an argument, so that the translation can be customised for different databases. dbplyr/inst/doc/new-backend.Rmd0000644000176200001440000000641714002647450016124 0ustar liggesusers--- title: "Adding a new DBI backend" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Adding a new DBI backend} %\VignetteEngine{knitr::rmarkdown} \usepackage[utf8]{inputenc} --- ```{r, echo = FALSE, message = FALSE} knitr::opts_chunk$set(collapse = T, comment = "#>") options(tibble.print_min = 4L, tibble.print_max = 4L) ``` This document describes how to add a new SQL backend to dbplyr. To begin: * Ensure that you have a DBI compliant database backend. If not, you'll need to first create it by following the instructions in `vignette("backend", package = "DBI")`. * You'll need a working knowledge of S3. Make sure that you're [familiar with the basics](https://adv-r.hadley.nz/s3.html) before you start. This document is still a work in progress, but it will hopefully get you started. I'd also strongly recommend reading the bundled source code for [SQLite](https://github.com/tidyverse/dbplyr/blob/master/R/backend-sqlite.R), [MySQL](https://github.com/tidyverse/dbplyr/blob/master/R/backend-mysql.R), and [PostgreSQL](https://github.com/tidyverse/dbplyr/blob/master/R/backend-postgres.R). ## First steps For interactive exploitation, attach dplyr and DBI. If you're creating a package, you'll need to import dplyr and DBI. ```{r setup, message = FALSE} library(dplyr) library(DBI) ``` Check that you can create a tbl from a connection, like: ```{r} con <- DBI::dbConnect(RSQLite::SQLite(), path = ":memory:") DBI::dbWriteTable(con, "mtcars", mtcars) tbl(con, "mtcars") ``` If you can't, this likely indicates some problem with the DBI methods. Use [DBItest](https://github.com/r-dbi/DBItest) to narrow down the problem. ## Write your first method The first method of your dbplyr backend should always be for the `dbplyr_edition()` generic: ```{r} #' @importFrom dbplyr dbplyr_edition #' @export dbplyr_edition.myConnectionClass <- function(con) 2L ``` This declares that your package uses version 2 of the API, which is the version that this vignette documents. ## Copying, computing, collecting and collapsing Next, check that `copy_to()`, `collapse()`, `compute()`, and `collect()` work: * If `copy_to()` fails, you probably need a method for `sql_table_analyze()` or `sql_table_index()`. If `copy_to()` fails during creation of the tbl, you may need a method for `sql_query_fields()`. * If `collapse()` fails, your database has a non-standard way of constructing subqueries. Add a method for `sql_subquery()`. * If `compute()` fails, your database has a non-standard way of saving queries in temporary tables. Add a method for `db_save_query()`. ## SQL translation Make sure you've read `vignette("translation-verb")` so you have the lay of the land. ### Verbs Check that SQL translation for the key verbs work: * `summarise()`, `mutate()`, `filter()` etc: powered by `sql_query_select()` * `left_join()`, `inner_join()`: powered by `sql_query_join()` * `semi_join()`, `anti_join()`: powered by `sql_query_semi_join()` * `union()`, `intersect()`, `setdiff()`: powered by `sql_query_set_op()` ### Vectors Finally, you may have to provide custom R -> SQL translation at the vector level by providing a method for `sql_translate_env()`. This function should return an object created by `sql_variant()`. See existing methods for examples. dbplyr/inst/doc/sql.Rmd0000644000176200001440000000603513474056125014546 0ustar liggesusers--- title: "Writing SQL with dbplyr" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Writing SQL with dbplyr} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` This vignette discusses why you might use dbplyr instead of writing SQL yourself, and what to do when dbplyr's built-in translations can't create the SQL that you need. ```{r setup, message = FALSE} library(dplyr) library(dbplyr) mf <- memdb_frame(x = 1, y = 2) ``` ## Why use dbplyr? One simple nicety of dplyr is that it will automatically generate subqueries if you want to use a freshly created variable in `mutate()`: ```{r} mf %>% mutate( a = y * x, b = a ^ 2, ) %>% show_query() ``` In general, it's much easier to work iteratively in dbplyr. You can easily give intermediate queries names, and reuse them in multiple places. Or if you have a common operation that you want to do to many queries, you can easily wrap it up in a function. It's also easy to chain `count()` to the end of any query to check the results are about what you expect. ## What happens when dbplyr fails? dbplyr aims to translate the most common R functions to their SQL equivalents, allowing you to ignore the vagaries of the SQL dialect that you're working with, so you can focus on the data analysis problem at hand. But different backends have different capabilities, and sometimes there are SQL functions that don't have exact equivalents in R. In those cases, you'll need to write SQL code directly. This section shows you how you can do so. ### Prefix functions Any function that dbplyr doesn't know about will be left as is: ```{r} mf %>% mutate(z = foofify(x, y)) %>% show_query() ``` Because SQL functions are general case insensitive, I recommend using upper case when you're using SQL functions in R code. That makes it easier to spot that you're doing something unusual: ```{r} mf %>% mutate(z = FOOFIFY(x, y)) %>% show_query() ``` ### Infix functions As well as prefix functions (where the name of the function comes before the arguments), dbplyr also translates infix functions. That allows you to use expressions like `LIKE` which does a limited form of pattern matching: ```{r} mf %>% filter(x %LIKE% "%foo%") %>% show_query() ``` Or use `||` for string concatenation (note that backends should translate `paste()` and `paste0()` for you): ```{r} mf %>% transmute(z = x %||% y) %>% show_query() ``` ### Special forms SQL functions tend to have a greater variety of syntax than R. That means there are a number of expressions that can't be translated directly from R code. To insert these in your own queries, you can use literal SQL inside `sql()`: ```{r} mf %>% transmute(factorial = sql("x!")) %>% show_query() mf %>% transmute(factorial = sql("CAST(x AS FLOAT)")) %>% show_query() ``` Note that you can use `sql()` at any depth inside the expression: ```{r} mf %>% filter(x == sql("ANY VALUES(1, 2, 3)")) %>% show_query() ``` dbplyr/inst/doc/translation-verb.R0000644000176200001440000000171414033043056016706 0ustar liggesusers## ---- message = FALSE--------------------------------------------------------- library(dplyr) con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") flights <- copy_to(con, nycflights13::flights) airports <- copy_to(con, nycflights13::airports) ## ----------------------------------------------------------------------------- flights %>% select(contains("delay")) %>% show_query() flights %>% select(distance, air_time) %>% mutate(speed = distance / (air_time / 60)) %>% show_query() ## ----------------------------------------------------------------------------- flights %>% filter(month == 1, day == 1) %>% show_query() ## ----------------------------------------------------------------------------- flights %>% arrange(carrier, desc(arr_delay)) %>% show_query() ## ----------------------------------------------------------------------------- flights %>% group_by(month, day) %>% summarise(delay = mean(dep_delay)) %>% show_query() dbplyr/inst/doc/translation-verb.html0000644000176200001440000004746014033043056017461 0ustar liggesusers Verb translation

Verb translation

There are two parts to dbplyr SQL translation: translating dplyr verbs, and translating expressions within those verbs. This vignette describes how entire verbs are translated; vignette("translate-function") describes how individual expressions within those verbs are translated.

All dplyr verbs generate a SELECT statement. To demonstrate we’ll make a temporary database with a couple of tables

library(dplyr)

con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:")
flights <- copy_to(con, nycflights13::flights)
airports <- copy_to(con, nycflights13::airports)

Single table verbs

  • select() and mutate() modify the SELECT clause:

    flights %>%
      select(contains("delay")) %>%
      show_query()
    ## <SQL>
    ## SELECT `dep_delay`, `arr_delay`
    ## FROM `nycflights13::flights`
    flights %>%
      select(distance, air_time) %>%  
      mutate(speed = distance / (air_time / 60)) %>%
      show_query()
    ## <SQL>
    ## SELECT `distance`, `air_time`, `distance` / (`air_time` / 60.0) AS `speed`
    ## FROM `nycflights13::flights`

    (As you can see here, the generated SQL isn’t always as minimal as you might generate by hand.)

  • filter() generates a WHERE clause:

    flights %>% 
      filter(month == 1, day == 1) %>%
      show_query()
    ## <SQL>
    ## SELECT *
    ## FROM `nycflights13::flights`
    ## WHERE ((`month` = 1.0) AND (`day` = 1.0))
  • arrange() generates an ORDER BY clause:

    flights %>% 
      arrange(carrier, desc(arr_delay)) %>%
      show_query()
    ## <SQL>
    ## SELECT *
    ## FROM `nycflights13::flights`
    ## ORDER BY `carrier`, `arr_delay` DESC
  • summarise() and group_by() work together to generate a GROUP BY clause:

    flights %>%
      group_by(month, day) %>%
      summarise(delay = mean(dep_delay)) %>%
      show_query()
    ## `summarise()` has grouped output by 'month'. You can override using the `.groups` argument.
    ## <SQL>
    ## SELECT `month`, `day`, AVG(`dep_delay`) AS `delay`
    ## FROM `nycflights13::flights`
    ## GROUP BY `month`, `day`

Dual table verbs

R SQL
inner_join() SELECT * FROM x JOIN y ON x.a = y.a
left_join() SELECT * FROM x LEFT JOIN y ON x.a = y.a
right_join() SELECT * FROM x RIGHT JOIN y ON x.a = y.a
full_join() SELECT * FROM x FULL JOIN y ON x.a = y.a
semi_join() SELECT * FROM x WHERE EXISTS (SELECT 1 FROM y WHERE x.a = y.a)
anti_join() SELECT * FROM x WHERE NOT EXISTS (SELECT 1 FROM y WHERE x.a = y.a)
intersect(x, y) SELECT * FROM x INTERSECT SELECT * FROM y
union(x, y) SELECT * FROM x UNION SELECT * FROM y
setdiff(x, y) SELECT * FROM x EXCEPT SELECT * FROM y

x and y don’t have to be tables in the same database. If you specify copy = TRUE, dplyr will copy the y table into the same location as the x variable. This is useful if you’ve downloaded a summarised dataset and determined a subset of interest that you now want the full data for. You can use semi_join(x, y, copy = TRUE) to upload the indices of interest to a temporary table in the same database as x, and then perform a efficient semi join in the database.

If you’re working with large data, it maybe also be helpful to set auto_index = TRUE. That will automatically add an index on the join variables to the temporary table.

Behind the scenes

The verb level SQL translation is implemented on top of tbl_lazy, which basically tracks the operations you perform in a pipeline (see lazy-ops.R). Turning that into a SQL query takes place in three steps:

  • sql_build() recurses over the lazy op data structure building up query objects (select_query(), join_query(), set_op_query() etc) that represent the different subtypes of SELECT queries that we might generate.

  • sql_optimise() takes a pass over these SQL objects, looking for potential optimisations. Currently this only involves removing subqueries where possible.

  • sql_render() calls an SQL generation function (sql_query_select(), sql_query_join(), sql_query_semi_join(), sql_query_set_op(), …) to produce the actual SQL. Each of these functions is a generic, taking the connection as an argument, so that the translation can be customised for different databases.

dbplyr/inst/doc/dbplyr.html0000644000176200001440000012440514033043053015453 0ustar liggesusers Introduction to dbplyr

Introduction to dbplyr

As well as working with local in-memory data stored in data frames, dplyr also works with remote on-disk data stored in databases. This is particularly useful in two scenarios:

  • Your data is already in a database.

  • You have so much data that it does not all fit into memory simultaneously and you need to use some external storage engine.

(If your data fits in memory there is no advantage to putting it in a database: it will only be slower and more frustrating.)

This vignette focuses on the first scenario because it’s the most common. If you’re using R to do data analysis inside a company, most of the data you need probably already lives in a database (it’s just a matter of figuring out which one!). However, you will learn how to load data in to a local database in order to demonstrate dplyr’s database tools. At the end, I’ll also give you a few pointers if you do need to set up your own database.

Getting started

To use databases with dplyr you need to first install dbplyr:

install.packages("dbplyr")

You’ll also need to install a DBI backend package. The DBI package provides a common interface that allows dplyr to work with many different databases using the same code. DBI is automatically installed with dbplyr, but you need to install a specific backend for the database that you want to connect to.

Five commonly used backends are:

  • RMariaDB connects to MySQL and MariaDB

  • RPostgres connects to Postgres and Redshift.

  • RSQLite embeds a SQLite database.

  • odbc connects to many commercial databases via the open database connectivity protocol.

  • bigrquery connects to Google’s BigQuery.

If the database you need to connect to is not listed here, you’ll need to do some investigation (i.e. googling) yourself.

In this vignette, we’re going to use the RSQLite backend which is automatically installed when you install dbplyr. SQLite is a great way to get started with databases because it’s completely embedded inside an R package. Unlike most other systems, you don’t need to setup a separate database server. SQLite is great for demos, but is surprisingly powerful, and with a little practice you can use it to easily work with many gigabytes of data.

Connecting to the database

To work with a database in dplyr, you must first connect to it, using DBI::dbConnect(). We’re not going to go into the details of the DBI package here, but it’s the foundation upon which dbplyr is built. You’ll need to learn more about if you need to do things to the database that are beyond the scope of dplyr.

library(dplyr)
con <- DBI::dbConnect(RSQLite::SQLite(), dbname = ":memory:")

The arguments to DBI::dbConnect() vary from database to database, but the first argument is always the database backend. It’s RSQLite::SQLite() for RSQLite, RMariaDB::MariaDB() for RMariaDB, RPostgres::Postgres() for RPostgres, odbc::odbc() for odbc, and bigrquery::bigquery() for BigQuery. SQLite only needs one other argument: the path to the database. Here we use the special string ":memory:" which causes SQLite to make a temporary in-memory database.

Most existing databases don’t live in a file, but instead live on another server. That means in real-life that your code will look more like this:

con <- DBI::dbConnect(RMariaDB::MariaDB(), 
  host = "database.rstudio.com",
  user = "hadley",
  password = rstudioapi::askForPassword("Database password")
)

(If you’re not using RStudio, you’ll need some other way to securely retrieve your password. You should never record it in your analysis scripts or type it into the console. Securing Credentials provides some best practices.)

Our temporary database has no data in it, so we’ll start by copying over nycflights13::flights using the convenient copy_to() function. This is a quick and dirty way of getting data into a database and is useful primarily for demos and other small jobs.

copy_to(con, nycflights13::flights, "flights",
  temporary = FALSE, 
  indexes = list(
    c("year", "month", "day"), 
    "carrier", 
    "tailnum",
    "dest"
  )
)

As you can see, the copy_to() operation has an additional argument that allows you to supply indexes for the table. Here we set up indexes that will allow us to quickly process the data by day, carrier, plane, and destination. Creating the right indices is key to good database performance, but is unfortunately beyond the scope of this article.

Now that we’ve copied the data, we can use tbl() to take a reference to it:

flights_db <- tbl(con, "flights")

When you print it out, you’ll notice that it mostly looks like a regular tibble:

flights_db 
#> # Source:   table<flights> [?? x 19]
#> # Database: sqlite 3.34.1 [:memory:]
#>    year month   day dep_time sched_dep_time dep_delay arr_time sched_arr_time
#>   <int> <int> <int>    <int>          <int>     <dbl>    <int>          <int>
#> 1  2013     1     1      517            515         2      830            819
#> 2  2013     1     1      533            529         4      850            830
#> 3  2013     1     1      542            540         2      923            850
#> 4  2013     1     1      544            545        -1     1004           1022
#> 5  2013     1     1      554            600        -6      812            837
#> 6  2013     1     1      554            558        -4      740            728
#> # … with more rows, and 11 more variables: arr_delay <dbl>, carrier <chr>,
#> #   flight <int>, tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>,
#> #   distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dbl>

The main difference is that you can see that it’s a remote source in a SQLite database.

Generating queries

To interact with a database you usually use SQL, the Structured Query Language. SQL is over 40 years old, and is used by pretty much every database in existence. The goal of dbplyr is to automatically generate SQL for you so that you’re not forced to use it. However, SQL is a very large language and dbplyr doesn’t do everything. It focusses on SELECT statements, the SQL you write most often as an analyst.

Most of the time you don’t need to know anything about SQL, and you can continue to use the dplyr verbs that you’re already familiar with:

flights_db %>% select(year:day, dep_delay, arr_delay)
#> # Source:   lazy query [?? x 5]
#> # Database: sqlite 3.34.1 [:memory:]
#>    year month   day dep_delay arr_delay
#>   <int> <int> <int>     <dbl>     <dbl>
#> 1  2013     1     1         2        11
#> 2  2013     1     1         4        20
#> 3  2013     1     1         2        33
#> 4  2013     1     1        -1       -18
#> 5  2013     1     1        -6       -25
#> 6  2013     1     1        -4        12
#> # … with more rows

flights_db %>% filter(dep_delay > 240)
#> # Source:   lazy query [?? x 19]
#> # Database: sqlite 3.34.1 [:memory:]
#>    year month   day dep_time sched_dep_time dep_delay arr_time sched_arr_time
#>   <int> <int> <int>    <int>          <int>     <dbl>    <int>          <int>
#> 1  2013     1     1      848           1835       853     1001           1950
#> 2  2013     1     1     1815           1325       290     2120           1542
#> 3  2013     1     1     1842           1422       260     1958           1535
#> 4  2013     1     1     2115           1700       255     2330           1920
#> 5  2013     1     1     2205           1720       285       46           2040
#> 6  2013     1     1     2343           1724       379      314           1938
#> # … with more rows, and 11 more variables: arr_delay <dbl>, carrier <chr>,
#> #   flight <int>, tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>,
#> #   distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dbl>

flights_db %>% 
  group_by(dest) %>%
  summarise(delay = mean(dep_time))
#> Warning: Missing values are always removed in SQL.
#> Use `mean(x, na.rm = TRUE)` to silence this warning
#> This warning is displayed only once per session.
#> # Source:   lazy query [?? x 2]
#> # Database: sqlite 3.34.1 [:memory:]
#>   dest  delay
#>   <chr> <dbl>
#> 1 ABQ   2006.
#> 2 ACK   1033.
#> 3 ALB   1627.
#> 4 ANC   1635.
#> 5 ATL   1293.
#> 6 AUS   1521.
#> # … with more rows

However, in the long-run, I highly recommend you at least learn the basics of SQL. It’s a valuable skill for any data scientist, and it will help you debug problems if you run into problems with dplyr’s automatic translation. If you’re completely new to SQL you might start with this codeacademy tutorial. If you have some familiarity with SQL and you’d like to learn more, I found how indexes work in SQLite and 10 easy steps to a complete understanding of SQL to be particularly helpful.

The most important difference between ordinary data frames and remote database queries is that your R code is translated into SQL and executed in the database on the remote server, not in R on your local machine. When working with databases, dplyr tries to be as lazy as possible:

  • It never pulls data into R unless you explicitly ask for it.

  • It delays doing any work until the last possible moment: it collects together everything you want to do and then sends it to the database in one step.

For example, take the following code:

tailnum_delay_db <- flights_db %>% 
  group_by(tailnum) %>%
  summarise(
    delay = mean(arr_delay),
    n = n()
  ) %>% 
  arrange(desc(delay)) %>%
  filter(n > 100)

Surprisingly, this sequence of operations never touches the database. It’s not until you ask for the data (e.g. by printing tailnum_delay) that dplyr generates the SQL and requests the results from the database. Even then it tries to do as little work as possible and only pulls down a few rows.

tailnum_delay_db
#> Warning: ORDER BY is ignored in subqueries without LIMIT
#> ℹ Do you need to move arrange() later in the pipeline or use window_order() instead?
#> # Source:     lazy query [?? x 3]
#> # Database:   sqlite 3.34.1 [:memory:]
#> # Ordered by: desc(delay)
#>   tailnum delay     n
#>   <chr>   <dbl> <int>
#> 1 <NA>    NA     2512
#> 2 N0EGMQ   9.98   371
#> 3 N10156  12.7    153
#> 4 N10575  20.7    289
#> 5 N11106  14.9    129
#> 6 N11107  15.0    148
#> # … with more rows

Behind the scenes, dplyr is translating your R code into SQL. You can see the SQL it’s generating with show_query():

tailnum_delay_db %>% show_query()
#> Warning: ORDER BY is ignored in subqueries without LIMIT
#> ℹ Do you need to move arrange() later in the pipeline or use window_order() instead?
#> <SQL>
#> SELECT *
#> FROM (SELECT `tailnum`, AVG(`arr_delay`) AS `delay`, COUNT(*) AS `n`
#> FROM `flights`
#> GROUP BY `tailnum`)
#> WHERE (`n` > 100.0)

If you’re familiar with SQL, this probably isn’t exactly what you’d write by hand, but it does the job. You can learn more about the SQL translation in vignette("translation-verb") and vignette("translation-function").

Typically, you’ll iterate a few times before you figure out what data you need from the database. Once you’ve figured it out, use collect() to pull all the data down into a local tibble:

tailnum_delay <- tailnum_delay_db %>% collect()
#> Warning: ORDER BY is ignored in subqueries without LIMIT
#> ℹ Do you need to move arrange() later in the pipeline or use window_order() instead?
tailnum_delay
#> # A tibble: 1,201 x 3
#>   tailnum delay     n
#>   <chr>   <dbl> <int>
#> 1 <NA>    NA     2512
#> 2 N0EGMQ   9.98   371
#> 3 N10156  12.7    153
#> 4 N10575  20.7    289
#> 5 N11106  14.9    129
#> 6 N11107  15.0    148
#> # … with 1,195 more rows

collect() requires that database does some work, so it may take a long time to complete. Otherwise, dplyr tries to prevent you from accidentally performing expensive query operations:

  • Because there’s generally no way to determine how many rows a query will return unless you actually run it, nrow() is always NA.

  • Because you can’t find the last few rows without executing the whole query, you can’t use tail().

nrow(tailnum_delay_db)
#> [1] NA

tail(tailnum_delay_db)
#> Error: tail() is not supported by sql sources

You can also ask the database how it plans to execute the query with explain(). The output is database dependent, and can be esoteric, but learning a bit about it can be very useful because it helps you understand if the database can execute the query efficiently, or if you need to create new indices.

Creating your own database

If you don’t already have a database, here’s some advice from my experiences setting up and running all of them. SQLite is by far the easiest to get started with. PostgreSQL is not too much harder to use and has a wide range of built-in functions. In my opinion, you shouldn’t bother with MySQL/MariaDB: it’s a pain to set up, the documentation is subpar, and it’s less featureful than Postgres. Google BigQuery might be a good fit if you have very large data, or if you’re willing to pay (a small amount of) money to someone who’ll look after your database.

All of these databases follow a client-server model - a computer that connects to the database and the computer that is running the database (the two may be one and the same but usually isn’t). Getting one of these databases up and running is beyond the scope of this article, but there are plenty of tutorials available on the web.

MySQL/MariaDB

In terms of functionality, MySQL lies somewhere between SQLite and PostgreSQL. It provides a wider range of built-in functions. It gained support for window functions in 2018.

PostgreSQL

PostgreSQL is a considerably more powerful database than SQLite. It has a much wider range of built-in functions, and is generally a more featureful database.

BigQuery

BigQuery is a hosted database server provided by Google. To connect, you need to provide your project, dataset and optionally a project for billing (if billing for project isn’t enabled).

It provides a similar set of functions to Postgres and is designed specifically for analytic workflows. Because it’s a hosted solution, there’s no setup involved, but if you have a lot of data, getting it to Google can be an ordeal (especially because upload support from R is not great currently). (If you have lots of data, you can ship hard drives!)

dbplyr/inst/doc/sql.R0000644000176200001440000000253014033043054014206 0ustar liggesusers## ---- include = FALSE--------------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ## ----setup, message = FALSE--------------------------------------------------- library(dplyr) library(dbplyr) mf <- memdb_frame(x = 1, y = 2) ## ----------------------------------------------------------------------------- mf %>% mutate( a = y * x, b = a ^ 2, ) %>% show_query() ## ----------------------------------------------------------------------------- mf %>% mutate(z = foofify(x, y)) %>% show_query() ## ----------------------------------------------------------------------------- mf %>% mutate(z = FOOFIFY(x, y)) %>% show_query() ## ----------------------------------------------------------------------------- mf %>% filter(x %LIKE% "%foo%") %>% show_query() ## ----------------------------------------------------------------------------- mf %>% transmute(z = x %||% y) %>% show_query() ## ----------------------------------------------------------------------------- mf %>% transmute(factorial = sql("x!")) %>% show_query() mf %>% transmute(factorial = sql("CAST(x AS FLOAT)")) %>% show_query() ## ----------------------------------------------------------------------------- mf %>% filter(x == sql("ANY VALUES(1, 2, 3)")) %>% show_query() dbplyr/inst/doc/new-backend.html0000644000176200001440000004277014033043054016342 0ustar liggesusers Adding a new DBI backend

Adding a new DBI backend

This document describes how to add a new SQL backend to dbplyr. To begin:

  • Ensure that you have a DBI compliant database backend. If not, you’ll need to first create it by following the instructions in vignette("backend", package = "DBI").

  • You’ll need a working knowledge of S3. Make sure that you’re familiar with the basics before you start.

This document is still a work in progress, but it will hopefully get you started. I’d also strongly recommend reading the bundled source code for SQLite, MySQL, and PostgreSQL.

First steps

For interactive exploitation, attach dplyr and DBI. If you’re creating a package, you’ll need to import dplyr and DBI.

library(dplyr)
library(DBI)

Check that you can create a tbl from a connection, like:

con <- DBI::dbConnect(RSQLite::SQLite(), path = ":memory:")
DBI::dbWriteTable(con, "mtcars", mtcars)

tbl(con, "mtcars")
#> # Source:   table<mtcars> [?? x 11]
#> # Database: sqlite 3.34.1 []
#>     mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
#>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1  21       6   160   110  3.9   2.62  16.5     0     1     4     4
#> 2  21       6   160   110  3.9   2.88  17.0     0     1     4     4
#> 3  22.8     4   108    93  3.85  2.32  18.6     1     1     4     1
#> 4  21.4     6   258   110  3.08  3.22  19.4     1     0     3     1
#> # … with more rows

If you can’t, this likely indicates some problem with the DBI methods. Use DBItest to narrow down the problem.

Write your first method

The first method of your dbplyr backend should always be for the dbplyr_edition() generic:

#' @importFrom dbplyr dbplyr_edition
#' @export
dbplyr_edition.myConnectionClass <- function(con) 2L

This declares that your package uses version 2 of the API, which is the version that this vignette documents.

Copying, computing, collecting and collapsing

Next, check that copy_to(), collapse(), compute(), and collect() work:

  • If copy_to() fails, you probably need a method for sql_table_analyze() or sql_table_index(). If copy_to() fails during creation of the tbl, you may need a method for sql_query_fields().

  • If collapse() fails, your database has a non-standard way of constructing subqueries. Add a method for sql_subquery().

  • If compute() fails, your database has a non-standard way of saving queries in temporary tables. Add a method for db_save_query().

SQL translation

Make sure you’ve read vignette("translation-verb") so you have the lay of the land.

Verbs

Check that SQL translation for the key verbs work:

  • summarise(), mutate(), filter() etc: powered by sql_query_select()
  • left_join(), inner_join(): powered by sql_query_join()
  • semi_join(), anti_join(): powered by sql_query_semi_join()
  • union(), intersect(), setdiff(): powered by sql_query_set_op()

Vectors

Finally, you may have to provide custom R -> SQL translation at the vector level by providing a method for sql_translate_env(). This function should return an object created by sql_variant(). See existing methods for examples.

dbplyr/inst/doc/sql.html0000644000176200001440000005403614033043054014761 0ustar liggesusers Writing SQL with dbplyr

Writing SQL with dbplyr

This vignette discusses why you might use dbplyr instead of writing SQL yourself, and what to do when dbplyr’s built-in translations can’t create the SQL that you need.

library(dplyr)
library(dbplyr)

mf <- memdb_frame(x = 1, y = 2)

Why use dbplyr?

One simple nicety of dplyr is that it will automatically generate subqueries if you want to use a freshly created variable in mutate():

mf %>% 
  mutate(
    a = y * x, 
    b = a ^ 2,
  ) %>% 
  show_query()
#> <SQL>
#> SELECT `x`, `y`, `a`, POWER(`a`, 2.0) AS `b`
#> FROM (SELECT `x`, `y`, `y` * `x` AS `a`
#> FROM `dbplyr_002`)

In general, it’s much easier to work iteratively in dbplyr. You can easily give intermediate queries names, and reuse them in multiple places. Or if you have a common operation that you want to do to many queries, you can easily wrap it up in a function. It’s also easy to chain count() to the end of any query to check the results are about what you expect.

What happens when dbplyr fails?

dbplyr aims to translate the most common R functions to their SQL equivalents, allowing you to ignore the vagaries of the SQL dialect that you’re working with, so you can focus on the data analysis problem at hand. But different backends have different capabilities, and sometimes there are SQL functions that don’t have exact equivalents in R. In those cases, you’ll need to write SQL code directly. This section shows you how you can do so.

Prefix functions

Any function that dbplyr doesn’t know about will be left as is:

mf %>% 
  mutate(z = foofify(x, y)) %>% 
  show_query()
#> <SQL>
#> SELECT `x`, `y`, foofify(`x`, `y`) AS `z`
#> FROM `dbplyr_002`

Because SQL functions are general case insensitive, I recommend using upper case when you’re using SQL functions in R code. That makes it easier to spot that you’re doing something unusual:

mf %>% 
  mutate(z = FOOFIFY(x, y)) %>% 
  show_query()
#> <SQL>
#> SELECT `x`, `y`, FOOFIFY(`x`, `y`) AS `z`
#> FROM `dbplyr_002`

Infix functions

As well as prefix functions (where the name of the function comes before the arguments), dbplyr also translates infix functions. That allows you to use expressions like LIKE which does a limited form of pattern matching:

mf %>% 
  filter(x %LIKE% "%foo%") %>% 
  show_query()
#> <SQL>
#> SELECT *
#> FROM `dbplyr_002`
#> WHERE (`x` LIKE '%foo%')

Or use || for string concatenation (note that backends should translate paste() and paste0() for you):

mf %>% 
  transmute(z = x %||% y) %>% 
  show_query()
#> <SQL>
#> SELECT `x` || `y` AS `z`
#> FROM `dbplyr_002`

Special forms

SQL functions tend to have a greater variety of syntax than R. That means there are a number of expressions that can’t be translated directly from R code. To insert these in your own queries, you can use literal SQL inside sql():

mf %>% 
  transmute(factorial = sql("x!")) %>% 
  show_query()
#> <SQL>
#> SELECT x! AS `factorial`
#> FROM `dbplyr_002`

mf %>% 
  transmute(factorial = sql("CAST(x AS FLOAT)")) %>% 
  show_query()
#> <SQL>
#> SELECT CAST(x AS FLOAT) AS `factorial`
#> FROM `dbplyr_002`

Note that you can use sql() at any depth inside the expression:

mf %>% 
  filter(x == sql("ANY VALUES(1, 2, 3)")) %>% 
  show_query()
#> <SQL>
#> SELECT *
#> FROM `dbplyr_002`
#> WHERE (`x` = ANY VALUES(1, 2, 3))
dbplyr/inst/doc/new-backend.R0000644000176200001440000000125414033043053015566 0ustar liggesusers## ---- echo = FALSE, message = FALSE------------------------------------------- knitr::opts_chunk$set(collapse = T, comment = "#>") options(tibble.print_min = 4L, tibble.print_max = 4L) ## ----setup, message = FALSE--------------------------------------------------- library(dplyr) library(DBI) ## ----------------------------------------------------------------------------- con <- DBI::dbConnect(RSQLite::SQLite(), path = ":memory:") DBI::dbWriteTable(con, "mtcars", mtcars) tbl(con, "mtcars") ## ----------------------------------------------------------------------------- #' @importFrom dbplyr dbplyr_edition #' @export dbplyr_edition.myConnectionClass <- function(con) 2L